前言

有关Material Design的使用方法,请自行参考这个链接

WPF使用Material Design

下面,直接上我碰到的问题及解决方式

问题1:手动配色

默认情况下,Material Design是提供了很多主题配色,但难免有些太过“出挑”,不适合工控软件的风格。
所以,下面简单介绍一下手动配色的基础方法:

先上完整的App.xaml

<?xml version="1.0" encoding="UTF-8"?>
<Application x:Class="TooksKit.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TooksKit"
             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
               
                <!--Material Design UI Package-->
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
               <!-- <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Indigo.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Teal.xaml" />-->
            </ResourceDictionary.MergedDictionaries>

           
            <!--PRIMARY-->
            <SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#4f83cc"/>
            <SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#01579b"/>
            <SolidColorBrush x:Key="PrimaryHueDarkBrush" Color="#002f6c"/>

            <SolidColorBrush x:Key="PrimaryHueLightForegroundBrush" Color="#ffffff"/>
            <SolidColorBrush x:Key="PrimaryHueMidForegroundBrush" Color="#ffffff"/>
            <SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#ffffff"/>
            
            <!--ACCENT-->
            <SolidColorBrush x:Key="SecondaryAccentBrush" Color="#4f83cc"/>
            <SolidColorBrush x:Key="SecondaryAccentForegroundBrush" Color="#4f83cc"/>

        </ResourceDictionary>

    </Application.Resources>
   
</Application>


上述代码段落中:

 <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />

代表采用的是Light主题,可供选择的还有适配夜晚的Dark的深色主题。这个做安卓、苹果的朋友应该更加清楚。
一般选择Light主题就行。

 <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />

代表使用MaterialDesignTheme.Defaults.xaml风格的控件。
注意,这个一般不改,除非你有能力自己二次创作控件UI及事件响应内容。

<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Indigo.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Teal.xaml" />

这两行就是指定Theme了,其中Primary是主要的,Accent是辅助。
话虽如此,但是具体使用也是看你自己的xaml View怎么写。有时候就喜欢调用Accent的配色,做个倔强的孩子。
我这里是把这两行注释了,因为将采用自己的深蓝色配色。

<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#4f83cc"/>
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#01579b"/>
<SolidColorBrush x:Key="PrimaryHueDarkBrush" Color="#002f6c"/>

上述三行xaml,一般决定了控件的颜色。
在你不指定某个颜色的情况下,系统会默认将UI控件染成“PrimaryHueMidBrush”的颜色。
至于如何手动指定,我后面会用解释。

 <SolidColorBrush x:Key="PrimaryHueLightForegroundBrush" Color="#ffffff"/>
 <SolidColorBrush x:Key="PrimaryHueMidForegroundBrush" Color="#ffffff"/>
 <SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#ffffff"/>

上述三行代码,决定了这个控件上的文字是什么颜色。
同样的,在你不指定某个颜色的情况下,一般默认使用PrimaryHueMidForegroundBrush的颜色。

请注意他们名称,细心地朋友已经发现,light、mid、dark是两两成对的。

##下面是简单的使用方式:

            <Grid Background="{DynamicResource PrimaryHueMidBrush}">

这行代码,就是指定当前的Grid背景色为PrimaryHueMidBrush指定的颜色。

问题2:UI界面、文字模糊

这个问题困扰了两天,经过多方排查,总算是找到了解决方案!

方案1 :materialDesign:Card 慎用

            <materialDesign:Card>
                你的界面内容
            </materialDesign:Card>

这个Card也算是这套UI的特色了吧?
但这玩意儿会导致严重的渲染模糊问题,尤其是在一些工控机上面(缺少运行环境、不联网、系统盗版等等)。
哪怕是使用不依赖框架的打包安装方式,也依旧会存在问题。
在我使用的过程中,自己电脑、同事电脑显示都很正常。
但在工控机上运行时,堪比马赛克画质。
用尽手段都无法修复,直到我开始排查这套UI自身控件渲染的问题,找到了这个罪魁祸首!
原因就是我在MainWindow.xaml 中,将card作为最外层,也就是最底层的画板。
圆角、垫高,虽然好看了,但也模糊了。
只要将它移除,并且,在Xaml编写的过程中,尽可能使用materialDesign的默认Style,也就是不显示指定控件的Style,问题将得到显著改善。
并且:
如果你使用了card,那么后续修复步骤的效果都将受到影响!
所以:
不要用card作为你的底层画布!!!
不要用card作为你的底层画布!!!
不要用card作为你的底层画布!!!
具体原因我也不清楚,有清楚的宝子可以留言告知一下。
至少让我活个明白。

方案2:在 MainWindow.xaml 的开头部分,添加这几行

TextOptions.TextFormattingMode="Display"
TextOptions.TextRenderingMode="ClearType"
UseLayoutRounding="True" 
SnapsToDevicePixels="True"

他们的作用可以自己去查,我这里就不赘述了,毕竟先解决问题,至于为什么,有兴趣的朋友自行了解。
最终得到的结果,像这样子:

<Window x:Class="DeviceToolsKit.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DeviceToolsKit"
        xmlns:pageViews ="clr-namespace:DeviceToolsKit.Views"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        WindowStartupLocation="CenterScreen" WindowState="Maximized"
        mc:Ignorable="d"
        Style="{StaticResource MaterialDesignWindow}"
        xmlns:library="clr-namespace:Library.Common;assembly=Library"
        FontFamily="{x:Static library:FontHelper.ProjectFontFamily}"
        TextElement.FontWeight="Normal"
        TextElement.FontSize="15"
        TextOptions.TextFormattingMode="Display"
        TextOptions.TextRenderingMode="ClearType"
        UseLayoutRounding="True" SnapsToDevicePixels="True"
        >

添加完成后,运行你的项目,字体模糊的情况应该会有改善。
如果效果不明显,别急,老夫还有后续手段!

方案3 中文显示有问题

需要指出的是,material design使用了Roboto字体,这个字体对于中文的支持不是很好。
部分字体会有明显的粗细不均、甚至错字。
所以,我们可以使用静态的字体资源,替换它的默认字体。
方法如下:

步骤1:添加第三方字体

我这里使用的是思源宋体
这是个开源的字体,可以使用bing去搜索(别用百度,跳出来的全是收费网址)。
它在谷歌的git上可以免费下载。
引入项目后,右键字体文件,设置属性:
在这里插入图片描述

步骤2:添加FontHelper静态类

直接上代码

  public static class FontHelper
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int WriteProfileString(string lpszSection, string lpszKeyName, string lpszString);

        [DllImport("gdi32")]
        private static extern int AddFontResource(string lpFileName);

        /// <summary>
        /// 自动安装UI使用的字体,防止因为字体缺失导致显示问题
        /// </summary>
        /// <returns>是否成功安装字体</returns>
        /// <exception cref="UnauthorizedAccessException">不是管理员运行程序</exception>
        /// <exception cref="Exception">字体安装失败</exception>
        public static ActionResult AutoInstallFonts()
        {
            ActionResult result = new ActionResult();
            result.IsSuccess = false;

            try
            {
                var appPath = FileHelper.GetAppDirectory();
                var subPath = "Resources\\Fonts";
                var fontFileNames = FileHelper.GetAllFileNames(appPath + subPath);
                int count = 0;
                foreach ( var fontFile in fontFileNames)
                {
                    string fontFilePath = FileHelper.BuildFileFullPath(appPath + subPath, fontFile);

                    System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();

                    System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
                    //判断当前登录用户是否为管理员
                    if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator) == false)
                    {
                        throw new UnauthorizedAccessException("当前用户无管理员权限,无法安装字体。");
                    }
                    //获取Windows字体文件夹路径
                    string fontPath = Path.Combine(System.Environment.GetEnvironmentVariable("WINDIR"), "fonts", Path.GetFileName(fontFilePath));
                    //检测系统是否已安装该字体
                    if (!File.Exists(fontPath))
                    {
                        count++;
                        // File.Copy(System.Windows.Forms.Application.StartupPath + "\\font\\" + FontFileName, FontPath); //font是程序目录下放字体的文件夹
                        //将某路径下的字体拷贝到系统字体文件夹下
                        File.Copy(fontFilePath, fontPath); //font是程序目录下放字体的文件夹
                        AddFontResource(fontPath);

                        //Res = SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); 
                        //WIN7下编译会出错,不清楚什么问题。注释就行了。  
                        //安装字体
                        WriteProfileString("fonts", Path.GetFileNameWithoutExtension(fontFilePath) + "(TrueType)", Path.GetFileName(fontFilePath));
                    }
                }

                if (count != 0)
                    result.ValueString = $"总计安装 {count} 款字体。";

                result.IsSuccess = true;

                return result;
            }
            catch (Exception ex)
            {
                var str = string.Format($"UI字体安装失败!原因:{ex.Message}");
                Log.Error(str);

                result.IsSuccess = false;
                result.ErrorMsg = str;
                return result;
            }
            
        }
        public static FontFamily ProjectFontFamily { get; }

        static FontHelper()
        {
            var fontPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Fonts\");
            ProjectFontFamily = new FontFamily(new Uri($"file:///{fontPath}"), "./#Source Han Serif SC");
            //ProjectFontFamily = new FontFamily(new Uri($"file:///{fontPath}"), "./#Roboto");
        }
    }

上述类中,附带了一个安装字体的方法,有需要的朋友可以直接调用。但需要注意字体文件存放的路径。
在这里,我们只用到了ProjectFontFamily 属性。
注意:

  • 需要注意你字体文件的存放路径,我是存放在Resources\Fonts文件夹下的。
var fontPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Fonts\");
  • 你的第三字体的名称(不是字体的文件名),下方代码的"./#Source Han Serif SC"。可以通过双击字体文件查看。
ProjectFontFamily = new FontFamily(new Uri($"file:///{fontPath}"), "./#Source Han Serif SC");

步骤3:使用静态字体

在MainWindow.xaml的开头部分,添加如下代码:

        xmlns:library="clr-namespace:Library.Common;assembly=Library"
        FontFamily="{x:Static library:FontHelper.ProjectFontFamily}"

主要是上方的第二句 FontFamily
我的项目里面,把FontHelper放在了Library类库项目里,所以,需要添加对应的命名空间,然后才能引用。
具体可以往上面翻翻,有完整的MainWindow.xaml 开头代码。

方案3: DPI自适应

如果你已经使用了前两个方案, 那么应该能够解决大部分问题。
但考虑到现在高DPI设备越来越多,而软件运行环境不确定的情况下,DPI自适应很有必要。
步骤如下:

步骤1:你需要一个app.manifest

如果你的项目已经有了这个文件,那么直接跳过这个步骤。
如果没有,那么右键项目,添加,新建项,选择app.manifest
在这里插入图片描述

步骤2:修改DPI感知的内容

双击进入app.manifest,找到有关DPI的部分。
这部分默认是被注释的。
我们直接用下面的代码替换即可。
win7或者win10的初代版本,会自动套用dpiAware 字段。
win10新版及以上,会套用dpiAwareness 字段。

  <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
       自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
       选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
       在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
       
       将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->

	<application xmlns="urn:schemas-microsoft-com:asm.v3">
		<windowsSettings>
			<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
			<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
		</windowsSettings>
	</application>


完成上述的“三板斧”,你的项目应该变清晰、适应DPI了。
如果还有问题,那么先确定你的项目框架,然后再去找对应的方法。

总结

Material Design是个好的UI套件。
但在使用的过程中,需要编程人员深入摸索。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐