[WPF]澳门美高梅手机网站使用WindowChrome自定义Window Style

1. 前言

做了WPF开发多年,一向从未自己实现一个自定义Window
Style,无论是《WPF编程宝典》或是各样博客都提出使用WindowStyle="None"
AllowsTransparency="True",于是想当然认为这样就可以了。如今来了胃口想自己实现一个,才晓得WindowStyle="None"
的章程根本不佳用,原因有几点:

  • 假诺Window没有阴影会很难看,但自己添加DropShadowEffect又非凡震慑属性。
  • 亟需自定义弹出、关闭、最大化、最小化动画,而团结做肯定不如Windows自带动画很快。
  • 内需实现Resize功用。
  • 其它BUG。

只可是性能问题就可以废弃WindowStyle="None"
的贯彻模式,幸好还有使用WindowChrome的落实形式,但一时之间也找不到精彩的实现,连MSDN上的文档(
WindowChrome
Class
.aspx)
)都太不合时宜,.NET
4.5也未曾SystemParameters2那一个类,只能参考一些开源项目(如 Modern UI for
WPF
)自己实现了。

  dotweb是16年业内托管到github的一个开源项目,go语言的web框架目前也有诸多,著名的有bee和echo。它们都是很理想的框架,然而我们欣赏更轻、更小的事物,经历一些之后大家更强调微服务这多少个设计意见。

2. Window基本成效

澳门美高梅手机网站 1

Window的基本成效如上图所示。注意除了专业的“最小化”、“最大化/还原”、”关闭”按钮外,Icon上单击还应该能开拓窗体的类别菜单,双击则一向关门窗体。

自家想实现类似Office
2016的Window效果:阴影、自定义窗体颜色。阴影、动画效果保留系统默认的就可以了,基本上会很耐看。
澳门美高梅手机网站 2

大部自定义Window都有圆角,但自己并不欣赏,低DPI的情况下只有多少个像素结合的圆角通常都不会很圆滑(如下图),所以保留直角。
澳门美高梅手机网站 3

另外,激活、非激活状态下标题栏颜色改变:
澳门美高梅手机网站 4

末段效果如下:
澳门美高梅手机网站 5

  dotweb是一个国人写的开源项目,据我所知它已经在多家商店的生育项目中使用,到最近截至它和早些年的web框架比显得很年轻,如今它还在以一种很便捷的迭代速度完美。我们计划会在2017年生产1.0本子,这时候dotweb可以满意各种现象下的效益要求。我们在圆满dotweb的长河中,轻巧是我们的标准化,我们不是要做一个大而全的web框架(因为前日曾经有这种框架了)我们要做的是一个小而精的框架,它会对扩张很团结,开发进度高速,性能特出。

3. 实现

  dotweb的githun地址是:https://github.com/devfeel/dotweb,我们欢迎您提出建议或者贡献代码,我们也欢迎您加入官方QQ群,群号可以在github上找到。

3.1 定义CustomWindow控件

首先,为了方便未来的恢宏,我定义了一个名为CustomWindow的模板化控件派生自Window。

public class CustomWindow : Window
{
    public CustomWindow()
    {
        DefaultStyleKey = typeof(CustomWindow);
        CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, CloseWindow));
        CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, MaximizeWindow, CanResizeWindow));
        CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, MinimizeWindow, CanMinimizeWindow));
        CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, RestoreWindow, CanResizeWindow));
        CommandBindings.Add(new CommandBinding(SystemCommands.ShowSystemMenuCommand, ShowSystemMenu));
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        base.OnMouseLeftButtonDown(e);
        if (e.ButtonState == MouseButtonState.Pressed)
            DragMove();
    }

    protected override void OnContentRendered(EventArgs e)
    {
        base.OnContentRendered(e);
        if (SizeToContent == SizeToContent.WidthAndHeight)
            InvalidateMeasure();
    }

    #region Window Commands

    private void CanResizeWindow(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = ResizeMode == ResizeMode.CanResize || ResizeMode == ResizeMode.CanResizeWithGrip;
    }

    private void CanMinimizeWindow(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = ResizeMode != ResizeMode.NoResize;
    }

    private void CloseWindow(object sender, ExecutedRoutedEventArgs e)
    {
        this.Close();
        //SystemCommands.CloseWindow(this);
    }

    private void MaximizeWindow(object sender, ExecutedRoutedEventArgs e)
    {
        SystemCommands.MaximizeWindow(this);
    }

    private void MinimizeWindow(object sender, ExecutedRoutedEventArgs e)
    {
        SystemCommands.MinimizeWindow(this);
    }

    private void RestoreWindow(object sender, ExecutedRoutedEventArgs e)
    {
        SystemCommands.RestoreWindow(this);
    }


    private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e)
    {
        var element = e.OriginalSource as FrameworkElement;
        if (element == null)
            return;

        var point = WindowState == WindowState.Maximized ? new Point(0, element.ActualHeight)
            : new Point(Left + BorderThickness.Left, element.ActualHeight + Top + BorderThickness.Top);
        point = element.TransformToAncestor(this).Transform(point);
        SystemCommands.ShowSystemMenu(this, point);
    }

    #endregion
}

首即便添加了多少个CommandBindings,用于给标题栏上的按钮绑定。

  dotweb和springmvc或者asp.net一样吗?它们是有两样的,springmvc和asp.net都是可怜优异的web框架,它们功用强大、性能优良、有着很高的支出效能,然则除此之外dotweb还有一个很可喜的特征——自宿主。有些朋友或者会纳闷什么叫做自宿主?倘若您领悟asp.net
core您应该明了asp.net
core可以退出iis启动,iis作为一个web服务器它就是web程序的宿主,自宿主就是可以脱离传统web服务器自己启动运作。所以说dotweb既是一个web框架又是一个web服务器,自宿主也不可以脱离web服务器,只然则它退出了观念的web服务器在中间集成了一个微型的web服务器。

3.2 使用WindowChrome

对于WindowChrome,MSDN.aspx)是这般描述的:

若要自定义窗口,同时保留其专业效率,能够采取WindowChrome类。
WindowChrome类窗口框架的职能分别开来视觉对象,并同意你决定的客户端和应用程序窗口的非工作区之间的境界。

在CustomWindow的DefaultStyle中添加正如Setting:

<Setter Property="WindowChrome.WindowChrome">
    <Setter.Value>
        <WindowChrome CornerRadius="0"
                      GlassFrameThickness="1"
                      UseAeroCaptionButtons="False"
                      NonClientFrameEdges="None" />
    </Setter.Value>
</Setter>

这般除了饱含阴影的边框,整个Window的内容就可以由用户定义了。

  oh,my
god!它不使用传统web服务器,它性能会不会很差?我如何相信它的特性呢?事实上你不需要对性能太操心,go语言本身性能就很高,dotweb是用原生的go语言实现,dotweb性能固然不令人惊艳也不会差。在已上线的项目中,dotweb表现很惬意,它在高并发的面貌中占据的服务器资源却很少,这和go语言的特征是严密的。在其后的版本迭代中,性能是大家最关心的业务,大家会着力的优化性能。

3.3 Window基本布局

<Border BorderBrush="{TemplateBinding BorderBrush}"
        BorderThickness="{TemplateBinding BorderThickness}"
        x:Name="WindowBorder">
    <Grid x:Name="LayoutRoot"
          Background="{TemplateBinding Background}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid x:Name="PART_WindowTitleGrid"
              Grid.Row="0"
              Height="26.4"
              Background="{TemplateBinding BorderBrush}">
           ....
        </Grid>
        <AdornerDecorator Grid.Row="1" KeyboardNavigation.IsTabStop="False">
            <ContentPresenter x:Name="MainContentPresenter"
                              KeyboardNavigation.TabNavigation="Cycle" />
        </AdornerDecorator>
        <ResizeGrip x:Name="ResizeGrip"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Bottom"
                    Grid.Row="1"
                    IsTabStop="False"
                    Visibility="Hidden"
                    WindowChrome.ResizeGripDirection="BottomRight" />
    </Grid>
</Border>

Window的正经布局很简短,大致上就是标题栏和内容。
PART_WindowTitleGrid是标题栏,具体内容下一节再研讨。

ContentPresenter的内容即Window的Client Area的范围。

ResizeGrip是当ResizeMode = ResizeMode.CanResizeWithGrip;时出现的Window右下角的大小调整手柄,基本上用于指示窗口能够透过拖动边框改调整小。
澳门美高梅手机网站 6

AdornerDecorator 为可视化树中的子元素提供
AdornerLayer,假如没有它的话一些装潢效果不可能显得(例如下图Button控件的Focus效果),Window的
ContentPresenter 外面套个 AdornerDecorator 是 必不可能忘的。
澳门美高梅手机网站 7

  dotweb还有一个亮点就是它很简单,假如你精通go语言,那么读懂dotweb的源码是件很容易的业务。我们追求的就是简单,我们不会为了炫耀一些技艺就把代码写得不那么易懂,我们只会用最精简的代码,您随时可以依据自己的要求修改dotweb的源码,这统统是可以的,dotweb不是板上钉钉的。

3.4 布局标题栏

<Button x:Name="Minimize"
        ToolTip="Minimize"
        WindowChrome.IsHitTestVisibleInChrome="True"
        Command="{Binding Source={x:Static SystemCommands.MinimizeWindowCommand}}"
        ContentTemplate="{StaticResource MinimizeWhite}"
        Style="{StaticResource TitleBarButtonStyle}"
        IsTabStop="False" />

标题栏上的按钮实现如上,将Command绑定到SystemCommands,并且安装WindowChrome.IsHitTestVisibleInChrome="True",标题栏上的情节要设置那多少个附加属性才能响应鼠标操作。

<Button VerticalAlignment="Center"
        Margin="7,0,5,0"
        Content="{TemplateBinding Icon}"
        Height="{x:Static SystemParameters.SmallIconHeight}"
        Width="{x:Static SystemParameters.SmallIconWidth}"
        WindowChrome.IsHitTestVisibleInChrome="True"
        IsTabStop="False">
    <Button.Template>
        <ControlTemplate TargetType="{x:Type Button}">
            <Image Source="{TemplateBinding Content}" />
        </ControlTemplate>
    </Button.Template>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <i:InvokeCommandAction Command="{x:Static SystemCommands.ShowSystemMenuCommand}" />
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{x:Static SystemCommands.CloseWindowCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

标题栏上的Icon也是一个按钮,单机打开SystemMenu,双击关闭Window。Height和Widht的值分别使用了SystemParameters.SmallIconHeightSystemParameters.SmallIconWidth,SystemParameters包含可用来查询系统安装的特性,能应用SystemParameters的地点尽量采纳总是不错的。

按钮的体裁没实现得很好,那一点暂时将就一下,未来立异吧。

  dotweb天生对分布式友好,你会意识它的session协助redis存储,以后版本补助基于cookie存储的session实现,更别说cache这类本身就需要分离的模块了。所以对将来业务增加或是集群架设,dotweb都有着很好的支撑。

3.5 处理Triggers

<ControlTemplate.Triggers>
    <Trigger Property="IsActive"
             Value="False">
        <Setter Property="BorderBrush"
                Value="#FF6F7785" />
    </Trigger>
    <Trigger Property="WindowState"
             Value="Maximized">
        <Setter TargetName="Maximize"
                Property="Visibility"
                Value="Collapsed" />
        <Setter TargetName="Restore"
                Property="Visibility"
                Value="Visible" />
        <Setter TargetName="LayoutRoot"
                Property="Margin"
                Value="7" />
    </Trigger>
    <Trigger Property="WindowState"
             Value="Normal">
        <Setter TargetName="Maximize"
                Property="Visibility"
                Value="Visible" />
        <Setter TargetName="Restore"
                Property="Visibility"
                Value="Collapsed" />
    </Trigger>
    <Trigger Property="ResizeMode"
             Value="NoResize">
        <Setter TargetName="Minimize"
                Property="Visibility"
                Value="Collapsed" />
        <Setter TargetName="Maximize"
                Property="Visibility"
                Value="Collapsed" />
        <Setter TargetName="Restore"
                Property="Visibility"
                Value="Collapsed" />
    </Trigger>

    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="ResizeMode"
                       Value="CanResizeWithGrip" />
            <Condition Property="WindowState"
                       Value="Normal" />
        </MultiTrigger.Conditions>
        <Setter TargetName="ResizeGrip"
                Property="Visibility"
                Value="Visible" />
    </MultiTrigger>
</ControlTemplate.Triggers>

虽然自己日常喜欢用VisualState的措施贯彻模板化控件UI再状态之间的变动,但奇迹如故Trigger方便快捷,尤其是不需要做动画的时候。
专注当WindowState=马克斯imized时要将LayoutRoot的Margin设置成7,尽管不这么做在最大化时Window边缘部分会被挡住,很多用到WindowChrome自定义Window的方案都未曾处理这一点。

  dotweb对长连接和websocket也有着很好的帮忙,实测单机百万长连接。

3.6 处理导航

另一些内需留意的是键盘导航。一般的话Window中按Tab键,焦点会在Window的内容间循环,不要让标题栏的按钮得到要旨,也无须让ContentPresenter
的逐一父元素拿到要旨,所以在ContentPresenter
上设置KeyboardNavigation.TabNavigation="Cycle"。为了不让标题栏上的逐条按钮得到要旨,在各样按钮上还安装了IsTabStop="False"

  dotweb内置了一个督察服务,通过它你可以查询dotweb的运行状态,那个服务咱们计划在此后的日子里加强功能,方便用户领悟dotweb的情状。

3.7 DragMove

稍稍人欣赏不止标题栏,按住Window的任何空白部分都足以拖动Window,只需要在代码中添加DragMove即可:

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    base.OnMouseLeftButtonDown(e);
    if (e.ButtonState == MouseButtonState.Pressed)
        DragMove();
}

  更多的dotweb特性您可以登录github的dotweb项目地址查看,地址早就在上方贴出。您也足以参预官方QQ群咨询关于dotweb问题,大家会提供答疑。本篇作品将是dotweb系列著作第一篇,在接下去的稿子中我会给我们详细介绍dotweb使用方法。重申以便,大家欢迎您对dotweb提议提出依然贡献代码,并且我们也感谢您指出提议或贡献源码,dotweb是一个开源、免费的品种,我们愿意它能给你带来有利。

3.8 移植TransitioningContentControl

干脆让Window打开时内容也添加一些动画。我将Silverlight
Toolkit的TransitioningContentControl复制过来,只改了某些卡通,并且在OnApplyTemplate()说到底添加了这句:VisualStateManager.GoToState(this, Transition, true);。最终将Window中的ContentPresenter
替换成这些控件,效果还不易(实际效果挺流畅的,不过GIF看起来不怎么着):
澳门美高梅手机网站 8

  思来想去如故写个hello,world吧,仿佛不写就不像是编程技术博客一样。

3.9 SizeToContent问题

有个相比较麻烦的题材,当设置SizeToContent="WidthAndHeight",打开Window会出现以下错误。
澳门美高梅手机网站 9

看上去是内容的Size和Window的Size总括错误,近来的缓解模式是在CustomWindow中添加以下代码,简单粗暴,但也许引发其它问题:

protected override void OnContentRendered(EventArgs e)
{
    base.OnContentRendered(e);
    if (SizeToContent == SizeToContent.WidthAndHeight)
        InvalidateMeasure();
}

  第一步:您可以实施go get -u github.com/devfeel/dotweb
命令安装dotweb。

5. 结语

率先次写Window样式,想不到遭受这样多需要小心的地点。
眼下只是个很简短的Demo,没有添加额外的法力,希望对客人有帮衬吗。
编码在Window10上做到,只在Windows7上稍加测试了弹指间,不敢保证兼容性。
如有错漏请指出。

  第二步:在您的go源文件中添加import “github.com/devfeel/dotweb”引用。

6. 参考

Window Styles and
Templates

WindowChrome
.aspx)
SystemParameters
.aspx)
mahapps.metro
Modern UI for WPF

  第三步:

7. 源码

GitHub – WindowDemo

func main(){
   dotapp := dotweb.New()
   dotapp.SetLogPath("/home/logs/wwwroot/")
   dotapp.HttpServer.Router().Get("/index",func(ctx *dotweb.HttpContext){
      ctx.WriteString("Hello,World!")
   })
   dotapp.StartServer(8080)
}

天涯论坛竟然不协理GO语言代码块,囧,在事后的篇章我会使用截图,但是幸而js的品格可以假装下Go语言代码块。

发表评论

电子邮件地址不会被公开。 必填项已用*标注