.Net中使用protobuf序列化:Google.Protobuf vs protobuf-net

旧地址:https://www.shisujie.com/blog/Google-ProtoBuf-vs-ProtoBuf-Net

Protocol Buffers - Google官方教程文档
示例源代码

什么是protobuf?

protobuf全称Protocol Buffers,由Google推出的一种平台、语言无关的数据交互格式。

为什么用protobuf

由于公司项目开发中,使用了WCF,其默认数据序列化是DataContractSerializer,采用xml格式,考虑到性能提升,于是采用了protobuf。

比较选择

protobuf的.net实现主要有两个版本:

  • Google.Protobuf Google.Protobuf
    • Google官方维护,项目前身是protobuf-csharp-port
    • 优势:在多平台协作开发时,定义一份协议,可以在各个语言中共用。
  • protobuf-net protobuf-net
    • 社区维护,主要由Marc Gravell管理维护
    • 优势:相对来说,更符合.net开发习惯;对于纯粹的.NET程序,使用起来更加方便

Google.ProtoBuf 如何使用

  • 安装Nuget包:Google.Protobuf和Google.Protobuf.Tools
1
2
3
4
5
6
7
8
9
10
11

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.12.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.12.3" />
</ItemGroup>
</Project>

  • 定义proto协议描述文件(.proto)

类似于.net中的类结构描述

1
2
3
4
5
6
7
8
9
10

syntax = "proto3";

package Jess.Sample.GoogleProtoBuf;

message Test {
int32 ID = 1;
string Name = 2;
}

  • 基于上面定义的proto文件,生成相应的C#类文件

命令行中执行如下代码:

1
2
3

<自己的protoc.exe文件目录>protoc.exe --proto_path=<proto文件所在目录> --csharp_out=<proto生成的cs文件目录> <定义的proto文件名>

或者参考如下powershell脚本内容

将脚本内容保存到项目目录下,直接执行ps1文件。

1
2
3
4
5
6
7
8
9
10

# 跳转到当前powershell脚本文件目录
$scriptpath = $PSScriptRoot
cd $scriptpath

# 获取用户目录
$userpath=$env:USERPROFILE
# 执行protoc生成类
&$userpath'\.nuget\packages\google.protobuf.tools\3.12.3\tools\windows_x64\protoc.exe' --proto_path=./protofile --csharp_out=./protoclass test.proto

  • 编写序列化相关代码
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 2;
test_forProtobuf.Name = "测试 Google.ProtoBuf";

using (MemoryStream memoryStream = new MemoryStream())
{
test_forProtobuf.WriteTo(memoryStream);
}
}

更多使用示例,见本文开头源码链接。


protobuf-net 如何使用

  • 安装Nuget包:protobuf-net
1
2
3
4
5
6
7
8
9
10

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.29" />
</ItemGroup>
</Project>

  • 编写待序列化的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

using ProtoBuf;

namespace Jess.Sample.ProtoBufNet
{
[ProtoContract]
public class Test
{
[ProtoMember(1)]
public int ID { get; set; }

[ProtoMember(2)]
public string Name { get; set; }
}
}

  • 实现序列化方法
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 1;
test_forProtobuf.Name = "测试 protobuf-net";

using (MemoryStream memoryStream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(memoryStream, test_forProtobuf);
}
}

更多使用示例,见本文开头源码链接。

Google.ProtoBuf 和 protobuf-net 性能比较

性能测试工具使用:BenchmarkDotNet

  • MemoryStream序列化

直接传入MemoryStream的WriteTo扩展方法,其实是官方内部封装了CodedOutputStream —— 从性能数据上看,也是无差别的。

1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Median Rank
Google.ProtoBuf使用MemoryStream 789.7 ns 15.72 ns 46.36 ns 775.3 ns 2
Google.ProtoBuf使用MemoryStream嵌套CodedOutputStream 799.6 ns 16.14 ns 46.81 ns 791.6 ns 2
protobuf-net使用MemoryStream 491.7 ns 7.13 ns 6.32 ns 488.8 ns 1
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 460.3 μs 14.65 μs 43.18 μs 1
protobuf-net使用FileStream 457.9 μs 15.37 μs 45.31 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 249.8 μs 4.97 μs 10.15 μs 1
protobuf-net使用FileStream 260.5 μs 4.84 μs 9.88 μs 2

上方是简易数据结构性能,下方是相对复杂一些的数据结构性能


注意事项【一些坑】:

  • proto3移除了required,对于required的作用,其实争议很大。proto3更加追求极简。
  • 但在我的之前遇到的情况,bool和enum未赋值的情况下,序列化时,默认值是有问题的。所以,单纯.net环境下,目前还是推荐proto2吧。
  • _:proto文件中下划线表示下划线后一个字母为大写。生成的类中,属性是无法带有下划线的。
  • 官方建议属性定义均用小写
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 470.3 μs 17.45 μs 51.47 μs 1
protobuf-net使用FileStream 466.9 μs 14.68 μs 43.28 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 281.3 μs 7.42 μs 21.88 μs 1
protobuf-net使用FileStream 283.1 μs 5.64 μs 13.29 μs 1

由此可见,两个库直接性能差距并不是很大,鉴于个人的使用场景,更加推荐protobuf-net。


掘:奇葩史

WiX Toolset 教程索引页

注意:虽然WiX Toolset功能强大,但其学习曲线相对较高。请慎重选择
若没有足够时间、没心思搞的请绕行至inno setup、installshield、nisi、setupfactory。。。

WiX Toolset 3.x 手册目录

入门

基础介绍

WixUI对话框库

WiX内置了一套Windows Installer安装包的用户界面库。本部分介绍关于使用WixUI对话框库的内容:

安装包捆绑包基础

使用技巧

本节包括怎样处理常见的WiX任务

文件、快捷方式和注册表

可再分发和安装检查

用户界面及本地化

产品更新

其他一般性处理

参考

  • Wix Toolset的nuget包:Wix Toolset
    • wix-nuget 源码
    • 之前Wix的nuget包Owner一直是kzu,大概2019年底修改成了wixtoolset和rob —— 这个nuget包目前应该还是kzu在维护。

掘:奇葩史

Wix Toolset基础 —— 环境变量设置示例

Wix Toolset基础 —— 环境变量设置示例

返回目录索引

本文由简至繁介绍如何在安装包中配置环境变量。
关于Environment元素的属性说明见:Wix Toolset基础 —— 环境变量设置

本文示例代码:Jess.Sample.Setup.Environment

基础配置示例

本文环境变量设置功能实现的是:将软件安装目录添加到系统环境变量 Path末尾处

首先先看下最简配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >

<Product Id="*" Name="环境变量设置Sample"
Language="2052" Codepage="936"
Version="1.0.0.0" Manufacturer="Jess"
UpgradeCode="{6C0C1D6D-80CF-4074-AC42-5ADBD7E4087F}">
<Package InstallerVersion="300" Compressed="yes" InstallScope="perMachine" SummaryCodepage="936" />

<MediaTemplate EmbedCab="yes" />

<UIRef Id="WixUI_Minimal"/>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />

<Feature Id="ProductFeature" Title="Wix环境变量设置" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>

<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLLOCATION" Name="EnvSample">
</Directory>
</Directory>
</Directory>
</Fragment>

<Fragment>
<ComponentGroup Id="ProductComponents" >
<Component Id="cmpEnvSample" Guid="{63B3AF3B-94BB-4D3D-8D24-A0772CA5CFAA}" Directory="INSTALLLOCATION">
<CreateFolder />
<!--系统环境变量Path中添加路径-->
<Environment Id="EnvSamplePath" Action="set" Part="last" Name="Path" Permanent="no" System="yes" Value="[INSTALLLOCATION]" />
</Component>
</ComponentGroup>
</Fragment>

</Wix>

其核心就一句话:<Environment Id="EnvSamplePath" Action="set" Part="last" Name="Path" Permanent="no" System="yes" Value="[INSTALLLOCATION]" />

控制环境变量设置时机

软件安装过程中,环境变量的设置,可能需要在某一步骤之后,即需要控制Wix安装的Action顺序。
例如环境变量的写入需要在卸载之后,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

<?define ProductVersion="1.0.0.0" ?>

<Product Id="*" Name="环境变量设置Sample"
Language="2052" Codepage="936"
Version="1.0.0.0" Manufacturer="Jess"
UpgradeCode="{6C0C1D6D-80CF-4074-AC42-5ADBD7E4087F}">
<Package InstallerVersion="300" Compressed="yes" InstallScope="perMachine" SummaryCodepage="936" />

<MediaTemplate EmbedCab="yes" />

<UIRef Id="WixUI_Minimal"/>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />

<Feature Id="ProductFeature" Title="Wix环境变量设置" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>

<Upgrade Id="{6C0C1D6D-80CF-4074-AC42-5ADBD7E4087F}">
<UpgradeVersion OnlyDetect='no' Property='SELFFOUND'
Minimum='$(var.ProductVersion)' IncludeMinimum='yes'
Maximum='$(var.ProductVersion)' IncludeMaximum='yes' />
<UpgradeVersion OnlyDetect='no' Property='PREVIOUSFOUND'
Minimum='0.0.0.1' IncludeMinimum='yes'
Maximum='$(var.ProductVersion)' IncludeMaximum='yes' />
<UpgradeVersion OnlyDetect='no' Property='NEWERFOUND'
Minimum='$(var.ProductVersion)' IncludeMinimum='yes' />
</Upgrade>

<InstallExecuteSequence>
<RemoveExistingProducts Before='InstallInitialize' />
<WriteEnvironmentStrings />
</InstallExecuteSequence>
</Product>

核心就是在InstallExecuteSequence中添加WriteEnvironmentStrings执行步骤,也可以直接设置Sequence属性:<WriteEnvironmentStrings Sequence="1" />

控制环境变量设置条件

在某些情况下,环境变量的设置需要添加条件判断,如通过Bootstrap安装时,外部传入的参数,标记不需要添加环境变量。

以下配置添加了EnableEnv属性控制是否设置环境变量:

1
2
3
4
5
6
7

<InstallExecuteSequence>
<WriteEnvironmentStrings>EnableEnv=1</WriteEnvironmentStrings>
</InstallExecuteSequence>

<Property Id="EnableEnv" Value="1"></Property>

另外,也可以直接设置Suppress属性禁用环境变量设置<WriteEnvironmentStrings Suppress="yes" />


理:奇葩史

[译]:Wix Toolset基础 —— 环境变量设置

[译]:Wix Toolset基础 —— 环境变量设置

返回目录索引

参考链接:

示例参考:

Environment简易说明

安装过程中要添加环境变量,需要在组件中使用Environment标签:

1
<Environment Id='UpdatePath' Name='PATH' Action='set' Permanent='yes' System='yes' Part='last' Value='[INSTALLDIR]' />
  • Action属性指定组件安装时,所执行的操作 —— createsetremove
  • Part属性控制设置新值的方式:all替换之前的值,first在现有值之前添加,last添加到现有值之后;
  • Permanent属性控制产品卸载时,环境变量的处理方式:yes保留环境变量值,no在产品卸载时,同时删除环境变量值;
  • System属性指定环境变量值是系统变量还是用户变量
  • 所有名称使用大写。 —— 经测试,目前小写名称也支持。

Environment元素结构

父级需要是Component

属性列表:

属性 类型 说明 是否必需
Id 字符串 Environment条目的唯一标识
Action 枚举

在安装父级组件时,指定环境变量的操作:createdsetremoved。此属性必须为以下的值:

  • create
    • 安装期间,若不存在此环境变量,则创建;若存在,则不影响已有的值。
  • set
    • 安装期间,若不存在此环境变量,则创建;若存在,则修改为新设置的值。
  • remove
    • 安装期间,移除环境变量。仅当环境变量的name和value均匹配时,才移除。若需要移除环境变量(无论它的值是什么),则不要设置Value属性。
Name 字符串 环境变量的名称
Part 枚举

此属性必须为以下的值:

  • all
    • 替换整个环境变量的值。此值为默认值。
  • first
    • 在现有值之前插入
  • last
    • 在现有值之后附加
Permanent YesNoType 指定在卸载时是否保留环境变量。
Separator 字符串 环境变量Value值的分隔符,默认时分号分隔。
System YesNoType 指定环境变量是否添加到系统环境变量。默认值为`no`,表示环境变量添加到用户变量中。
Value 字符串 待设置到环境变量中的值。若此属性为设置,则在安装过程中移除已存在的同名环境变量。

译:奇葩史

Git 2.27.0 使用Git-SVN时,引发E235000错误问题解决

Git 2.27.0 使用Git-SVN时,引发E235000错误问题解决

缘由

今天更新VSCode 1.47.0之后,一直提示git 2.26.2版本有已知问题,建议更新;本着相信Microsoft的心态,更新了Git 2.27.0版本。

问题

Git更新为2.27.0之后,之前用git-svn连接的库,不能git svn rebasegit svn dcommit了,一直报如下错误:

1
E235000: In file 'subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.c' line 1666: assertion failed (get_current_pool_cb != NULL)

解决

将Git 2.27.0卸载,重新安装2.26.2版本后,问题解决。

下载链接


解:奇葩史

互联网大厂项目开源地址索引

互联网大厂项目开源地址索引

含部分非纯互联网大厂

国外

国内


掘:奇葩史

电脑突然断电,代码文件损坏,如何恢复,求解

电脑突然断电,代码文件损坏,如何恢复,求解

情况:

vs2015开发wpf程序,程序已编译,即文件已经保存,电脑突然断电,一个cs文件内容被清空,但大小与原来的一样。

解决及问题:

  • 1、 网络查找“相似问题”

有很多人遇到此类问题,,回答的人都在扯,都在说没保存的问题,什么要记得ctrl+s,这帮都是问题都看不清的自以为是的那啥。看得那疼。。。。

  • 2、 使用文本文件恢复工具,找了一个叫File Repair的软件,说的好听,但没效果,另外还有其他的一些恢复工具,没有去试,我以前搞过磁盘恢复,那些挺耗时的,而且也不保证能够恢复,那些恢复软件原理就是依赖你的磁盘的某一个区域没有格式化。

【据我了解,磁盘恢复就相当于,一般的删除文件只是把指向这个文件的索引删除,但那个文件其实还在,只是系统没有索引指向它,而恢复工具,就是从磁盘上找到这块内容,重新给他加上索引】
例如:文件在磁盘上存储的是010101,一般的删除,磁盘上还是010101,所以恢复工具找到它就可以恢复,而如果被覆盖为111101 or 低格为000000,那谁都木有办法恢复了。

  • 3、 鉴于上面提到的文本文件恢复工具,自己决定看下损坏文件的内容,利用16进制查看工具Hex Workshop Hex Editor查看文件内容,发现内容被全部置为0,即内容是真的清空了 —— 一开始yy想法是:要是单纯某些字节损坏,或许修改某些字节,还可能恢复。

  • 4、 最终解决方案:

    • 1、由于个人在开发时,习惯本地用git进行版本控制,故有一些历史记录可以用,先用最近的历史记录进行恢复,但缺失最新【未提交】的内容。
    • 2、打开ILSpy工具(.net 反编译工具),找到编译过的程序,然后找到对应文件的反编译内容,在.net中,反编译的代码与原始代码一致性还是比较高的,故可以利用这一点,获得最新的一部分代码。
  • 5、 仍然存在的问题:开发中,常常因为尝试步骤比较多,所以会有大量的注释代码,但这些临时注释代码并不是无用的,而反编译的代码中不会有注释代码,所以,最后获得的代码,仍然有缺失,无法找回。

故,为了以后再次遇到此类情况,及时求解:大家有啥好方法,可以恢复这些损坏的文件,拿出来一起分享下。
or 聊聊是什么机制导致断电将文件编码全置0
or 吐槽下自己遇到的类似经历

扯:奇葩史

设置TextBlock默认样式后,其他控件的Text相关属性设置失效问题

设置TextBlock默认样式后,其他控件的Text相关属性设置失效问题

问题:

  • 定义了默认TextBlock样式后,再次自定义下拉框 or 其他控件 ,当内部含有TextBlock时,设置控件的字体相关样式无效,系统始终使用TextBlock设置默认样式

解决方案:

  • 为相关控件定义数据模板,为内部TextBlock添加样式资源,指向默认资源。

具体为啥会有这种问题不清楚,解决方案参考:https://bbs.csdn.net/topics/390262033

1
2
3
4
5
6
7
8
9
<DataTemplate x:Key="DataTemplate3">
<Grid>
<TextBlock >
<TextBlock.Resources>
<Style TargetType="{x:Type TextBlock}"/>
</TextBlock.Resources>
</TextBlock>
</Grid>
</DataTemplate>

建议:

  • 尽量不要定义TextBlock默认样式,宁可在每个控件上设置Style属性,或者将使用TextBlock的地方改用Label,然后定义Label的默认样式【不过个人觉得一般情况还是不要使用Label,毕竟TextBlock是最简的,消耗的电脑资源也最小】

解:奇葩史

WPF中自定义标题栏时窗体最大化处理之WindowChrome

WPF中自定义标题栏时窗体最大化处理之WindowChrome

注意:

  • 本文方法基础是WindowChrome,而WindowChrome在.NET Framework 4.5之后才集成发布的。见:WindowChrome Class
  • .NET Framework 4.0中使用WindowChrome,需要安装Ribbon来支持WindowChrome
  • 目前官方文档的内容较为陈旧(但仍有参考价值),其中提到了SystemParameters2,这个应该是Ribbon里的东西,4.5想用可以安装Xceed.Wpf.AvalonDock库,这里面有现成的Microsoft.Windows.Shell.SystemParameters2实现——当然,自己不怕麻烦,可以自己实现来获取要用的系统变量。(一般用不着,4.5SystemParameters添加了许多系统变量的实现)

实现步骤

第一步:基本实现【保留系统基础操作按钮】

  • 添加Window的Style定义,并设置WindowChrome.WindowChrome属性;
  • 设置WindowChrome标题栏:
    • CaptionHeight——主要用于拖动有效区;
    • GlassFrameThickness——影响标题栏系统按钮显示,0表示不使用系统按钮【后面介绍】,-1表示用的系统默认值,如下示例则表示标题栏高度30;
    • 自定义窗体Title

注意:控件模板中的定义:

  • 1、最外层Border背景无颜色,否则会覆盖标题栏,看不到系统按钮。
  • 2、内部布局定义,使用Grid隔出30的标题栏高度,也可以直接对ContentPresenter设置Margin【主要是为了让顶部显示出标题栏】。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Style x:Key="WindowStyle1" TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30" GlassFrameThickness="0,30,0,0"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
<AdornerDecorator >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="1"/>
<TextBlock Text="{TemplateBinding Title}" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

存在的问题: 最大化边框问题 —— 会有部分溢出。

根据观察,这个溢出宽度应该是8,此值的来源:(SystemParameters.MaximizedPrimaryScreenWidth - SystemParameters.WorkArea.Width)/2 ,高度也可以这样计算。

第二步:优化边界处理

  • 方法1:模板添加最大化触发器,设置最大化时,内部布局Margin设为8
  • 方法2:模板添加最大化触发器,设置最大化时,限制布局最大化的宽高最大值

不管使用哪种方法,最大化时,系统的标题栏高度都发生了变化,故,需要重新设置模板中定义的标题栏高度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
<AdornerDecorator >
<Grid Name="win_content">
<Grid.RowDefinitions>
<RowDefinition Height="30" x:Name="row_title"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="1"/>
<TextBlock Text="{TemplateBinding Title}" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>
</AdornerDecorator>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="Margin" TargetName="win_content" Value="8"/>
<!--二选一-->
<Setter Property="MaxWidth" TargetName="win_content" Value="{Binding Source={x:Static SystemParameters.WorkArea},Path=Width}" />
<Setter Property="MaxHeight" TargetName="win_content" Value="{Binding Source={x:Static SystemParameters.WorkArea},Path=Height}"/>

<Setter Property="Height" TargetName="row_title" Value="22" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

至此,都是系统标题栏和我们自定义内容组合使用,但这样总有些边边角角要修正,下面就完全自定义标题栏

第三步:完全自定义标题栏【即,不使用系统的操作按钮】

  • 初步操作类似第一步,其中将GlassFrameThickness设置为0
  • 在内容定义部分添加自定义的标题栏,添加操作按钮,并设置按钮属性WindowChrome.IsHitTestVisibleInChrome="True"

如果不设置WindowChrome.IsHitTestVisibleInChrome,则由于我们之前设置CaptionHeight,则这个区域内,按钮将失效。
但是,也不能将整个标题栏布局设置这个属性,那样会完全覆盖系统标题栏的操作,如拖动效果,即CaptionHeight设置的那个区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--样式定义-->
<Style x:Key="WindowStyle2" TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome UseAeroCaptionButtons="False" GlassFrameThickness="0" CaptionHeight="30" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" >
<AdornerDecorator >
<ContentPresenter x:Name="win_content" />
</AdornerDecorator>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="Margin" TargetName="win_content" Value="8"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
1
2
3
4
5
6
7
8
9
10
<!--定义标题栏-->
<Grid Background="Red" >
<Grid Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="Blue">
<StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True" HorizontalAlignment="Right" >
<Button Name="btn_Min" Content="—" ></Button>
<Button Name="btn_Max" Content="☐" ></Button>
<Button Name="btn_Close" Content="✕" ></Button>
</StackPanel>
</Grid>
</Grid>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//标题栏按钮功能实现
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();

this.btn_Min.Click += Btn_Min_Click;
this.btn_Max.Click += Btn_Max_Click;
this.btn_Close.Click += Btn_Close_Click;
}

private void Btn_Close_Click(object sender, RoutedEventArgs e)
{
this.Close();
}

private void Btn_Max_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Maximized == this.WindowState ? WindowState.Normal : WindowState.Maximized;
}

private void Btn_Min_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
}

关联了解:
如果你平时也使用Visual Studio Code【VSCode】,文中的第一步和第三步,在vscode的设置中有对应效果:

  • 第一步 —— 将vscode用户设置中Title Bar Style值设为native;
  • 第三步 —— 将vscode用户设置中Title Bar Style值设为custom。

相关下载

点击查看完整源代码

解:奇葩史


落后要挨打,个人以前一直局限于老旧方式实现窗体处理,没有跟进WPF技术改进,在此,非常感谢@vbfool的提醒。【之前说周末搞出来,自己这几周浪了,没研究。今天趁着别人双11买买买,咱就撸撸代码】

WPF中窗体最大化问题处理

WPF中窗体最大化问题处理

遇到的问题信息

  • 问题:当WindowStyle=None时,窗口最大化,不显示任务栏 —— 即窗体是全屏效果。
  • 解决中遇到的问题列表【主要涉及到任务栏发生改变后的一些问题处理】:
    • 最大化时,任务栏被遮盖;
    • 最大化后,拖动任务栏,无法自适应窗体;
    • 最大化后,拖动任务栏,窗体还原,还原数据丢失,始终显示最大;
    • 最大化后,拖动任务栏,窗体还原,设置之前保存的窗体位置数据,再次设置,由于和之前一样,窗体位置信息不生效;

解决方案

  • 思路:窗体最大化时,将窗体透明化,设置内部元素Grid的Margin属性,从而显示出任务栏
  • 步骤:

1、 设置窗体相关属性:WindowStyle="None" AllowsTransparency="True" Background="Transparent" ResizeMode="CanMinimize"

窗体需要支持透明,并将窗体设置为透明;设置ResizeMode,否则最大化时,边框会有影响。

2、 添加窗体最大化/还原代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
double normaltop;
double normalleft;
double normalwidth;
double normalheight;
/// <summary>
/// 最大化/还原处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Btn_maximize_Click(object sender, RoutedEventArgs e)
{
//wpf最大化 全屏显示任务栏处理
if (this.WindowState == WindowState.Normal)
{
normaltop = this.Top;
normalleft = this.Left;
normalwidth = this.Width;
normalheight = this.Height;

double top = SystemParameters.WorkArea.Top;
double left = SystemParameters.WorkArea.Left;
double right = SystemParameters.PrimaryScreenWidth - SystemParameters.WorkArea.Right;
double bottom = SystemParameters.PrimaryScreenHeight - SystemParameters.WorkArea.Bottom;
gd_main.Margin = new Thickness(left, top, right, bottom);

this.WindowState = WindowState.Maximized;
}
else
{
this.WindowState = WindowState.Normal;

//必须先设置为0,在重新设值,若前后值一样,会失效 --拖动任务栏后,还原-始终显示在屏幕最左上方
this.Top = 0;
this.Left = 0;
this.Width = 0;
this.Height = 0;

this.Top = normaltop;
this.Left = normalleft;
this.Width = normalwidth;
this.Height = normalheight;

gd_main.Margin = new Thickness(0);
}
}

3、添加任务栏变化处理

注意:此节实现仅适用于.Net Framework 4.5及以上。因为4.0及以前的版本中不包含StaticPropertyChanged事件。
不过可以通过WndProc来自己实现,其中会有一个问题:利用WndProc来监听,结果是比较实时的,而SystemParameters中的值,实时性可能会跟不上,从而获取到的值仍然是旧的。

对此有两种解决方案:

  • 1、添加一个Timer 或者 直接Sleep,等待一下在SystemParameters的值【此法相对简单,但无法完全保证有效,毕竟SystemParameters中值更新的时间,还是要看.Net Framework】;
  • 2、利用Windows API读取系统值,用方法SystemParametersInfo获取SPI_GETWORKAREA【微软官方其实就是用这个获取的,这样比我下面的运行效率还要高些】

另外:下面的方法可能会有系统兼容性问题,我在Windows 10上是通过的,但在Windows 8.1上,边界存在问题【不确定是不是Framework在系统上有bug】

注册事件:SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "WorkArea")
{
if (this.WindowState == WindowState.Maximized)
{
double top = SystemParameters.WorkArea.Top;
double left = SystemParameters.WorkArea.Left;
double right = SystemParameters.PrimaryScreenWidth - SystemParameters.WorkArea.Right;
double bottom = SystemParameters.PrimaryScreenHeight - SystemParameters.WorkArea.Bottom;
gd_main.Margin = new Thickness(left, top, right, bottom);
}
}
}

相关下载

点击查看完整源代码

解:奇葩史