Σ( ° △ °**     **)

使用 WPF + Material Design 构建应用

主页 Material Design In XAML Toolkit

Icon Material Design Icons

案例教程 【Youtube】Design com WPF

源码地址【GitHub】Design com WPF

其他选择

mahapps.metro——a UI toolkit for WPF

DMSkin WPF 样式 UI 框架

发挥想象

一些技巧

圆角窗口
<Window ...
        WindowStyle="None" Background="Transparent" AllowsTransparency="True">
    <Border Background="White" CornerRadius="15,15,15,15">
    	<Grid>
        	<!--主界面-->
        </Grid>
    <Border/>
</Window>
改变 ToolBar 方向(利用ToolBarTray)
<ToolBarTray x:Name="toolBarTray" Orientation="Vertical">
	<ToolBar>
	</ToolBar>
</ToolBarTray>
动态向Toolbar添加按钮,按钮实现右键菜单动态删除自身效果
private void AddButton_Click(object sender, RoutedEventArgs e)
{
    Button button = new Button();
    button.Name = "BUTTON_NAME";
    button.Content = button.Name;

    MenuItem deleteMenu = new MenuItem			//新建一个菜单子项
    {
        Header = "删除",
        FontFamily = new FontFamily("微软雅黑"),
        FontWeight = FontWeights.Bold,
    };
    deleteMenu.Click += DeleteMenu_Click;		//alt+enter自动生成待实现方法
    
    button.ContextMenu = new ContextMenu(); 	//添加右键菜单
    button.ContextMenu.Items.Add(deleteMenu);	//菜单子项添加进按钮右键菜单

    this.ToolBar.Items.Add(button);
    this.ToolBar.RegisterName(button.Name, button);//注册之后可以通过findName找到目标控件
}

private void DeleteMenu_Click(object sender, RoutedEventArgs e)
{
    var mi = sender as MenuItem;
    var cm = mi.Parent as ContextMenu;
    var button = cm.PlacementTarget as Button;
    this.ToolBar.UnregisterName(button.Name);
    this.ToolBar.Items.Remove(button);
}

Tip:通过右键删除自身的关键是获取到右键菜单源控件的引用,值得一提的是 DeleteMenu_Click 方法中获取源控件的方式并不通用,假设你通过 Toolbar 自动给每一个子项添加右键菜单,上述代码 PlacementTarget 只能获取到 ToolBar 的引用。一种更加通用的做法是为按钮添加一个 MouseDown 事件(在对按钮做任何按键动作都会触发此方法,为什么不是 RightMouseDown 事件因为它会拦截右键的动作而不会触发右键菜单),此时获取按钮的引用(sender as Button)存储到一个固定的位置(例如父容器 ToolBar.Tag 中),当触发右键菜单点击事件时就可以获取到源控件引用,不过要确保那个位置不会被其他代码使用。

划分网格+填充图片
//2X2网格
RowDefinition row1 = new RowDefinition();
RowDefinition row2 = new RowDefinition();
ColumnDefinition col1 = new ColumnDefinition();
ColumnDefinition col2 = new ColumnDefinition();
GRID_ID.RowDefinitions.Add(row1);
GRID_ID.RowDefinitions.Add(row2);
GRID_ID.ColumnDefinitions.Add(col1);
GRID_ID.ColumnDefinitions.Add(col2);
GRID_ID.ShowGridLines = true;

Image image1 = new Image
{
	Source = PictureURI(@"IMAGE_PATH"),
    Stretch = Stretch.Fill,  //图片拉伸至控件大小
    Margin = new Thickness(6),
};
image1.SetValue(Grid.RowProperty, 0);
image1.SetValue(Grid.ColumnProperty, 0);
GRID_ID.Children.Add(image1);
在 Canvas 中添加一个可拖拽移动的控件
<Grid Name="EMap_Container" Grid.Column="1">
    <Canvas Name="EMapArea">
        <!--绑定Canvas父元素容器长宽属性使得Canvas中的背景元素大小可以自适应-->
        <Grid Width="{Binding ActualWidth, ElementName=EMap_Container}" 
              Height="{Binding ActualHeight, ElementName=EMap_Container}">
            <!--ZIndex=-1作为背景-->
            <Image Panel.ZIndex="-1"
                   Source=@"\Source\Image.jpg" 
                   Stretch="Fill"/>
        </Grid>
    </Canvas>
</Grid>
private void EMap_Button_Cctv_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (e.ClickCount == 2)
    {
        Image image = new Image { 
            Width = 30, Height = 30, 
            Source = new BitmapImage(new Uri(@"\Source\Icon\Cam.gif")) 
        };
        //设置元素在Canvas中的位置
        Canvas.SetLeft(image, 20);
        Canvas.SetTop(image, 20);
        //元素赋予可拖拽移动的behavior
        Interaction.GetBehaviors(image).Add(
            new MouseDragElementBehavior() { ConstrainToParentBounds = true });
        EMapArea.Children.Add(image);
    }
}

参考列表

合理分层

避免循环依赖(职责分配)

设计先行

实现现行

良好的API

使用 IOC 维护实例

持久化你的数据

数据库不是唯一选择

小心编码

使用Linq to SQL 实现 CRUD

在 VisualStudio 中使用 GIT 进行源码管理

参考列表

  1. 使用 GIT 进行源码管理 —— 在 VisualStudio 中使用 GIT
  2. VS2017 使用 Git 进行源代码管理

单元测试

创建单元测试

使用日志

查看异常信息

单元测试不总是顺利的,一般而言都会出现以下“常见的”提示框(NullReferenceException真程序员噩梦)

我们需要定位异常位置,点击查看详细信息

展开$exception,找到StackTrace,点击右侧的放大镜按钮,就可以阅读栈信息。

根据栈信息定位到问题代码(不过IDEA就直接可以看异常栈,VS也应该可以,不过没找到位置)

勘测现场——自动窗口

尽可能利用框架提供的一切

有关WPF生命周期与钩子方法

不需要记住,当需要的时候只需查看Window类定义即可

参考列表

  1. 【microsoft官方文档】WPF Windows 概述
  2. 【microsoft官方文档】WPF 与 Xamarin.Forms 应用程序生命周期

利用 Windows API 进行身份验证

如果使用过 SQL Server 在登录时会提供两种登录方式: Windows 身份验证 或 SQL Server 身份验证。在我们构建自己的应用的时候同样可以提供 Windows 身份验证 这种验证方式,实际上这种验证方式的代码编写并不困难。

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool LogonUser(string lpszUsername, string lpszDomain, 
                                    string lpszPassword, int dwLogonType, 
                                    int dwLogonProvider, out int phToken);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CloseHandle(IntPtr phToken);

[DllImport("kernel32.dll")]
static extern uint GetLastError();

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
    string username = USERNAME_TEXTBOX.Text;//USERNAME_TEXTBOX 是你的用户名输入框的name值

    string password = PASSWORDBOX.Password;

    string Domain = System.Environment.UserDomainName;

    int token;

    if (LogonUser(name, Domain, password, 2, 0, out token)) {
        CloseHandle((IntPtr)token);
        Login();
    }
    else
        MessageBox.Show("登录失败, 错误代码:" + GetLastError());
}

Tips:这只是示例,你还需要将以上代码进行合理封装。例如你可以新建一个 AppSecurity 项目,其中提供多种验证方式,同时这个项目暴露一个容易使用的 API 供用户层(也就是你的WPF、Winform等工程)使用,这样你可以将用户层代码和底层实现分开,之后的工程维护可以轻松不少,替换用户层实现或者底层实现变得更加容易

参考列表

  1. C# 调用 windows 系统内部的身份验证方法
  2. C# 调用 windows 本身的 logonUser 方法
  3. 使用 Win32API LogonUser 在 C# 程序中进行域认证
  4. 处理 Windows 不允许 LogonUser 以空的密码登录问题
  5. 获取 windows 当前登录的用户名

  6. 获得本机计算机名字,本机当前系统登陆用户和判断管理员权限
  7. WinApi - GetLastError vs. Marshal.GetLastWin32Error
  8. Windows 错误码大全
  9. 【Microsoft】LogonUserA function

出现了!STA

Application.Current.Dispatcher.Invoke(()=> {
    //操作界面控件
});

使用算法加密传输数据

顺水推舟——拦截消息并自定义处理

进一步解耦:MSMQ

利用windows性能监视器监视性能

在WPF中使用WinForm控件

首先需要引入 WindowsFormsIntegration.dll 和 System.Windows.Forms.dll 两个 Dll。引用-右键-添加引用-搜索对应的 dll 名称即可,并在主界面 xaml 中添加以下内容

xmlns:wfi ="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:wf ="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

WPF 中使用 Winform 控件首先要添加 WinForm 控件的宿主容器 WindowsFormsHost,用于衔接 WPFWinForm

在 xaml 中使用 Winform 控件(利用 WindowsFormsHost)。

<wfi:WindowsFormsHost x:Name="winFormsHost" Grid.Row="0" Grid.Column="1" Width="Auto">
    <wf:PictureBox x:Name="WinformPictureBox"/>
</wfi:WindowsFormsHost>

c# 代码中使用 Winform PictureBox 控件,注意的是不要导入 Winform 控件的命名空间会和 WPF 的控件命名空间冲突。

System.Windows.Forms.PictureBox image = new System.Windows.Forms.PictureBox
{
	Image = System.Drawing.Image.FromFile(@"IMAGE_PATH"),
    SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage
};
WindowsFormsHost windowsFormsContainer = new WindowsFormsHost
{
    Child = image
};
windowsFormsContainer.SetValue(Grid.ColumnProperty, 1);
windowsFormsContainer.SetValue(Grid.RowProperty, 1);
WPF_GRID.Children.Add(windowsFormsContainer);

参考了 【CSDN】在 WPF 中使用 WinForm 控件方法【CSDN】C# 下 WPF 中调用 WinForm 控件

升级 .NET 版本出现错误

你的项目中有N个模块,因为某种需要你需要升级某个模块.NET版本(例如从.NET 4.5 升级为 .NET 4.6.1),但是升级之后发现编译错误,错误信息大意为——升级之后导致项目版本不一致问题:该框架版本高于当前目标框架“.NETFramework,Version=v4.5”

解决方案是首先清理该模块之前编译的版本,之后查找是否有引用该模块的其他部分,这些部分的 .NET 版本同样需要更新。之后整体工程重新编译即可。

最后!美好的明天