应用在那里苹果手机下载软件不见了怎么添加怎么是空白的?

前言
对现代化Windows桌面应用而言,越来越多的应用程序采用Hybrid混合架构,即原生客户端技术+Web网页技术嵌入的混合模式提供应用服务,这样既有原生技术先天的端能力优势,又有来自Web技术的快速开发、灵活部署的优势。
目前主流的方案肯定是"基于嵌入式Chromium框架(简称CEF)"解决方案,但是带来的问题就是程序安装包体积巨大,因为它必须要把整个Chromium内核完整的打包进去,而微软原生控件WebView或者WebBrowser控件由于其技术或背后浏览器框架跟不上现代步伐,很难满足实际场景需求,那么随着Microsoft Edge积极采用Chromium内核,并被Windows 10/11内置,基于它诞生了WebView2这个控件,未来我们有希望可以直接通过使用WebView2来替代CEF,从而大幅降低混合架构开发模式下的安装包体积,提高程序运行效率。
采用WebView2的核心优势:1、缩小应用程序安装包体积大小。2、降低应用程序磁盘空间占用。3、节约Hybrid架构开发实现成本。4、减少应用分发的CDN流量消耗。5、优化浏览器运行内核维护成本。
核心提要:1、从Windows 11开始的操作系统版本将直接内置WebView2运行时;2、Microsoft 365应用程序v2101版本已开始依赖WebView2运行时提供和Web无差体验的新功能和特性;3、截止到目前,WebView2运行时已被超过2亿台Windows设备部署。4、WebView2运行时自带对H264编码的支持,无需额外编译配置。
什么是Microsoft Edge WebView2
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/
Microsoft Edge WebView2控件允许在本机应用中嵌入Web技术(HTML、CSS以及JavaScript)。WebView2控件使用Microsoft Edge(Chromium)作为绘制引擎,以在本机应用中显示Web内容。使用WebView2,可以在本机应用的不同部分嵌入Web代码,或在单个WebView实例中生成所有本机应用。什么是WebView2运行时
https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/#download-section
WebView2运行时简介"WebView2运行时(Webview2 Runtime)"是一个可再发行运行时,并充当WebView2(或)Web平台的基础组件。此概念类似于Visual C++/.NET应用的.NET运行时。"WebView2运行时"包含经过修改的Microsoft Edge(Chromium)二进制文件,这些二进制文件针对WebView2应用进行了微调和测试。安装WebView2运行时后,它不会显示为用户可见的浏览器应用。例如,用户没有浏览器桌面快捷方式或"开始"菜单中的条目。有两种不同的方法将"WebView2运行时"分发和更新到客户端计算机:常青分发模式和离线分发模式。常青分发模式(Evergreen Runtime)在“常青分发模式(Evergreen Runtime)”下,WebView2运行时不与你的应用打包,但最初使用联机引导程序或脱机安装程序安装到客户端上。之后,WebView2运行时将在客户端计算机上自动更新。然后,你可以从最新的WebView2 SDK分发使用最新WebView2 API的WebView2应用更新。建议大多数开发人员使用常青分发模式。
优点:
基础Web平台(WebView2运行时)自动更新,无需你进行更多工作。
客户端系统上WebView2运行时所需的磁盘空间更少,因为WebView2运行时由客户端上的所有WebView2应用共享。
在符合条件的系统上,Microsoft Edge和Evergreen WebView2运行时的二进制文件在同一版本上时硬链接在一起。此链接为磁盘占用、内存和性能带来了好处。
缺点:
WebView2应用不能指定需要WebView2运行时的特定版本。
离线分发模式(Offline Runtime)在“离线分发模式(Offline Runtime)”下,下载特定版本的WebView2运行时,并随应用包中的WebView2应用一起打包它。随应用打包的WebView2运行时仅由WebView2应用使用,而客户端计算机上任何其他应用不会使用。
优点:
你可以更加控制WebView2运行时的版本控制。你知道哪些WebView2 API可用于你的应用,因为你控制哪个版本的WebView2运行时可用于你的应用。你的应用无需测试是否有最新的API。
缺点:
你需要自己管理WebView2运行时。WebView2运行时不会在客户端上自动更新,因此若要使用最新的WebView2 API,必须定期更新应用以及更新后的WebView2运行时。
如果安装了多个WebView2应用,则客户端上需要更多磁盘空间。
离线分发运行时无法通过使用安装程序进行安装。
宣告超过2亿设备的覆盖
https://blogs.windows.com/msedgedev/2021/08/31/webview2-windows-app-sdk-winui2-runtime-cdp-helper/
我们一直在努力提高WebView2运行时在Windows机器上的可用性。我们很高兴地宣布这项工作的两项更新。首先,WebView2运行时将在Windows11机器中内置。其次,我们看到许多应用程序,包括Microsoft Office,开始将WebView2 Runtime与其应用程序一起部署。迄今为止,WebView2 Runtime已安装在超过2亿台Windows设备上! WebView2 Runtime的日益普及将使以首选的Evergreen分发模式部署WebView2应用程序变得更加容易。WebView2和Microsoft 365应用版Microsoft 365应用开始提供依赖"WebView2运行时(Webview2 Runtime)"的新功能或改进功能。例如,Outlook中的会议室查找器和会议Insights功能。WebView2是Microsoft Edge使用的渲染引擎,在桌面应用程序中显示基于Web的功能。通过使用"WebView2运行时(Webview2 Runtime)",我们可以更轻松地为您的用户提供跨设备平台外观和感觉相同的Office功能。反过来,这种一致的体验可帮助您的用户学习和使用这些功能,而无需了解每个设备平台上Office的细微差别。例如,通过使用"WebView2运行时(Webview2 Runtime)",在运行Windows的设备上使用Outlook和在Web上使用Outlook时,房间查找器功能看起来相同。Office加载项也将开始依赖"WebView2运行时(Webview2 Runtime)"。WebView2要求在运行Office的设备上安装"WebView2运行时(Webview2 Runtime)"。如果设备上未安装"WebView2运行时(Webview2 Runtime)",您的用户将无法使用依赖于WebView2的Office功能。因此,在2021年4月,我们开始在运行Windows且安装了Microsoft 365应用程序版本2101或更高版本的设备上安装"WebView2运行时(Webview2 Runtime)"。
重要
"WebView2运行时(Webview2 Runtime)"不会在设备上安装Microsoft Edge(完整浏览器),并且不需要在设备上安装Microsoft Edge。
在设备上安装"WebView2运行时(Webview2 Runtime)"后,不会更改用户的默认浏览器选择。
官方示例
https://github.com/MicrosoftEdge/WebView2Samples
勤学勤练
https://github.com/TaylorShi/HelloWebView2
创建解决方案及目录1. 新建名为"HelloWebView2"的解决方案dotnet new sln -o HelloWebView2
2. 切换到"HelloWebView2"目录cd .\HelloWebView2\
创建.Net Core的Wpf项目1. 创建名为"demoForWpfCore"的Wpf项目dotnet new wpf -o demoForWpfCore -f net5.0
2. 添加"demoForWpfCore"到解决方案dotnet sln add .\demoForWpfCore\demoForWpfCore.csproj
3. 切换到"demoForWpfCore"目录cd .\demoForWpfCore\
4. 运行"demoForWpfCore"项目dotnet watch run
创建WinUI 3的桌面项目1. 添加WinUI3的空白项目在解决方案上右键,添加 => 新建项目,筛选C#语言,Windows平台,WinUI项目类型。选择"打包的空白应用(桌面版WinUI 3)(Blank App, Packaged(WinUI 3 in desktop))"项目类型,然后单击"下一步"按钮。创建名为demoForWinUi3的项目。创建成功之后,会发现多了两个项目,一个是demoForWinUi3桌面项目,一个是demoForWinUi3 (Package)打包项目。创建.Net Framework的Winforms项目1. 创建名为"demoForWinFormFrame"的WinForms项目
这里需要将框架最低设置为:.Net Framework 4.5,这是目前WebView2的WinFroms包最低兼容版本。
2. 运行"demoForWinFormFrame"项目.Net Framework WinForms项目安装WebView2包
https://www.nuget.org/packages/Microsoft.Web.WebView2
在demoForWinFormFrame项目右键进入"管理Nuget程序包"。搜索关键词WebView2即可找到Microsoft.Web.WebView2这个包,安装即可。初探嵌入WebView2控件双击打开MainForm.cs文件,打开窗体设计视图。在Visual Studio顶部菜单的"视图" => "工具栏",这时候我们会看到顶部会多出来一个WebView2 Windows Forms Control组,里面有个控件叫WebView2控件。我们把它拖到右侧的窗体中,并且填充显示,并且我们给他取名为WebViewForMain,设置其初始的Source值为https://www.bing.com。接下来,我们运行看看效果给WebView添加导航功能为了更好的展示WebView2的相关能力,我们当然需要给它插上导航的翅膀,为此我们需要构建一个可输入的面板和导航按钮。1. 使用字体图标来构建按钮,准备字体资源首先,我们还是需要引入SegoeFluentIcons.ttf这个字体图标文件,我们把它放在根目录的Fonts文件夹中,生成操作需设置成"内容",复制到输出目录设置为"始终复制"。然后我们需要借助一个IconfontHelper的类来读取字体资源。public class IconfontHelper
{
//提供一个字体系列集合,该集合是基于客户端应用程序提供的字体文件生成的。
private static System.Drawing.Text.PrivateFontCollection pfcc;
public static System.Drawing.Text.PrivateFontCollection PFCC
{
get { return pfcc ?? LoadFont(); }
}
public static System.Drawing.Text.PrivateFontCollection LoadFont()
{
pfcc = new System.Drawing.Text.PrivateFontCollection();
pfcc.AddFontFile(Environment.CurrentDirectory + "/Fonts/SegoeFluentIcons.ttf");
return pfcc;
}
}
2. 实验性的在WinForms上支持字体图标,并构建按钮我们先尝试通过Panel + Label的组合来实现一个字体图标的按钮效果。从左侧工具箱中拖取两个控件组合成上诉截图效果,然后在MainForm的Load函数中,我们需要给Label挂载图标字体和指定图标。public MainForm()
{
InitializeComponent();
Load += MainForm_Load;
}
private void MainForm_Load(object sender, EventArgs e)
{
InitButtonStyle();
}
具体初始化按钮样式的方法如下:/// <summary>
/// 初始化按钮样式
/// </summary>
private void InitButtonStyle()
{
#region InitButtonStyle
// 后退按钮
TextBlockForNaviBack.Text = "\ue0a6";
TextBlockForNaviBack.Font = new Font(IconfontHelper.PFCC.Families[0], 24);
// 前进按钮
TextBlockForNaviForward.Text = "\ue0ab";
TextBlockForNaviForward.Font = new Font(IconfontHelper.PFCC.Families[0], 24);
// 停止按钮
TextBlockForNaviStop.Text = "\ue106";
TextBlockForNaviStop.Font = new Font(IconfontHelper.PFCC.Families[0], 26);
// 刷新按钮
TextBlockForNaviRefresh.Text = "\ue149";
TextBlockForNaviRefresh.Font = new Font(IconfontHelper.PFCC.Families[0], 24);
// 主页按钮
TextBlockForNaviHome.Text = "\ue10f";
TextBlockForNaviHome.Font = new Font(IconfontHelper.PFCC.Families[0], 24);
// 搜索按钮
TextBlockForNaviTarget.Text = "\uf78b";
TextBlockForNaviTarget.Font = new Font(IconfontHelper.PFCC.Families[0], 24);
#endregion
}
查看下运行效果:效果还算让人满意。
这里有个技巧就是,我应该如何得到每个图形对应的这个字符文本,这里我找到一个能够预览字体图标的小网站IconFont Preview By Luckly,进入后,我们选择解析本地的ttf文件。
然后选中前面的SegoeFluentIcons.ttf文件上传并解析,然后它会把字体中所有图标的Unicode编码展示出来,这里我们以前进和后退两个图标为例,我们会看到他们的编码都是以&#xe开头和;结尾的,我们只需要提取剩下的字符,加上前缀\ue即可,比如前进按钮编码&#xe0ab;而言,最终的编码为\ue0ab,依次类推即可。为Windows 11风格构建圆角按钮和圆角输入框控件,并自定义响应事件
我们知道,Win10是直角风格,但是Win11开始微软开始推行圆角,甚至默认窗体,你原来是直角的都会自动给你加成圆角。那么我们也想办法来构建一组圆角的控件,查了一些资料,说实话没有找到特别满意的方案,最终找了个妥协的,那就是依靠绘制来做的一个圆角Panel来构建控件的圆角,它还有个缺点就是不太方便去改变颜色了。public class CornerRadiusPanel: Panel
{
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRoundedRectangle(new SolidBrush(Color.White), 10, 10, this.Width - 40, this.Height - 60, 10);
SolidBrush brush = new SolidBrush(
Color.Transparent
);
g.FillRoundedRectangle(brush, 12, 12, this.Width - 44, this.Height - 64, 10);
g.DrawRoundedRectangle(new Pen(ControlPaint.Light(Color.Transparent, 0.00f)), 12, 12, this.Width - 44, this.Height - 64, 10);
g.FillRoundedRectangle(new SolidBrush(Color.Transparent), 12, 12 + ((this.Height - 64) / 2), this.Width - 44, (this.Height - 64) / 2, 10);
}
}
首先我们新建一个名为CornerRadiusPanel的自定义控件,让它继承自Panel,通过重写OnPaint这个事件来实现圆角的绘制,绘制的底色暂且先用白色Color.White,这里还依赖一个全局静态帮助类GraphicsExtension,有了它,我们便可以构建一个圆角的面板。基于它,我们结合Label和TextBox这两个自带控件,分别组建自定义控件LabelButton和CornerTextbox,都用这个CornerRadiusPanel做圆角的底盘。控件的相对位置可能需要耐心的调整,为了更加精致一点,这里我们的LabelButton控件采用45x45的尺寸,CornerTextbox控件采用603x50的尺寸,其中内嵌的TextBox字体大小采用20pt。.Net Core Wpf项目添加并使用WebView2控件.Net Core的WPF项目安装WebView2包
https://www.nuget.org/packages/Microsoft.Web.WebView2
a. 命令行安装"Microsoft.Web.WebView2"dotnet add package Microsoft.Web.WebView2
b. 或者项目右键Nuget包管理,通过可视化界面安装"Microsoft.Web.WebView2"c. 安装之前,Bin目录结构d. 安装之后,Bin目录结构发现,新增了Microsoft.Web.WebView2.Core.dll、Microsoft.Web.WebView2.WinForms.dll、Microsoft.Web.WebView2.Wpf.dll这三个文件。e. 安装之后,运行效果f. 命令行打开项目位置explorer.exe .
初探嵌入WebView2控件
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/get-started/wpf
在demoForWpfCore项目的MainWindow.xaml文件中。1. 新增引用"Microsoft.Web.WebView2.Wpf"的命名空间xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
2. 添加"WebView2"控件即可,其中"Source"便是启动时加载的网址设定<Window
x:Class="demoForWpfCore.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:demoForWpfCore"
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800"
>
<Grid>
<wpf:WebView2 Source="https://www.bing.com"/>
</Grid>
</Window>
3. 运行着"WebView2"控件的实际效果给WebView添加导航功能为了更好的展示WebView2的相关能力,我们当然需要给它插上导航的翅膀,为此我们需要构建一个可输入的面板和导航按钮。1. 添加Gird布局,将WebView和操作面板上下拆分<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
x:Name="GirdForOperate"
>
</Grid>
<wpf:WebView2
x:Name="WebViewForMain"
Grid.Row="1"
Source="https://www.bing.com"
/>
</Grid>
2. 在操作面板添加TextBox地址输入框和导航按钮这里我们为了美观一点,采用Border包起来,并且设置一定的圆角,而且采用Gird来做左右布局。<Grid
Grid.Row="0"
x:Name="GirdForOperate"
Margin="8,4"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border
x:Name="BorderForSource"
CornerRadius="4"
Grid.Column="0"
Height="44"
BorderBrush="Gray"
BorderThickness="1"
Padding="4"
>
<TextBox
x:Name="TextBoxForSource"
BorderThickness="0"
TextAlignment="Left"
TextWrapping="NoWrap"
Padding="0,6,0,4"
Text=""
FontSize="18"
KeyDown="TextBoxForSource_KeyDown"
/>
</Border>
<Border
x:Name="BorderForNavi"
CornerRadius="4"
Grid.Column="2"
BorderBrush="#0780d8"
BorderThickness="1"
Background="#39baf4"
Padding="4"
MouseDown="BorderForNavi_MouseDown"
>
<TextBlock
x:Name="TextBlockForNavi"
Text="导航"
Width="100"
FontSize="18"
Background="Transparent"
Foreground="White"
TextAlignment="Center"
VerticalAlignment="Center"
/>
</Border>
</Grid>
这里我们给BorderForNavi控件挂载一个BorderForNavi_MouseDown事件,给TextBoxForSource控件挂载一个TextBoxForSource_KeyDown事件。实际效果如下:3. 程序启动的时候,自动把当前WebView的网址填写到网址输入框中public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
TextBoxForSource.Text = WebViewForMain.Source?.ToString();
}
4. 响应导航按钮的BorderForNavi_MouseDown点击事件private void BorderForNavi_MouseDown(object sender, MouseButtonEventArgs e)
{
var sourceContext = TextBoxForSource.Text?.Trim();
WebViewForMain.CoreWebView2.Navigate(sourceContext);
}
这里需要用到WebView控件实例的CoreWebView2对象的Navigate方法。5. 响应地址输入框的TextBoxForSource_KeyDown回车事件通常,根据用户的使用习惯,我们输入新的地址后会习惯性的回车,那么我们支持下这个习惯,增加对"地址输入框"的回车事件支持,这里运用控件"按键触发(KeyDown)"事件来做,判断e.Key == Key.Enter的情况即表示触发了回车事件。private void TextBoxForSource_KeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
BorderForNavi_MouseDown(null, null);
}
}
6. 优化窗体启动位置、窗体大小和名称<Window
...
Title="WebView2浏览器"
Height="800"
Width="1367"
WindowStartupLocation="CenterScreen"
WindowState="Normal"
/>
最终效果如下图:修改地址栏内容并回车尝试WPF上实现Windows 11的Mica风格最近朋友分享关于一个在WPF上实现Windows 11的Mica风格的演示项目。
https://github.com/Difegue/Mica-WPF-Sample
它的文章发布在Apply Mica to a WPF app on Windows 11a. 新建名为demoForWpfCoreModernUI的Wpf的.Net Core 5.0的项目dotnet new wpf -o demoForWpfCoreModernUI -f net5.0
dotnet sln add .\demoForWpfCoreModernUI\demoForWpfCoreModernUI.csproj
b. 修改demoForWpfCoreModernUI项目的目标框架这里你可能会问,为什么要改这个?嗯,我试过,如果TargetFramework是net5.0-windows的时候,安装ModernWpfUI这个组件会跑不起来。
无法引用ModernWpf.dll,因为它使用了对WinRT的内置支持,而.NET 5和更高版本中不再支持它。需要支持.NET 5的更新版本组件。更多信息查看Built-in support for WinRT is removed from .NET
但是我发现Mica-WPF-Sample项目是可以用的,最终发现它虽然也是使用.Net 5,但是指定了更具体的一个版本,也许是被微软拦截之前的。所以,这里我们也将demoForWpfCoreModernUI项目的目标框架修改为这个net5.0-windows10.0.18362.0。<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows10.0.18362.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
c. 安装ModernWpfUI包,引入App资源实际上,要在Wpf里面开启对Mica的支持是不需要用到它的,但是作者说,要实现对黑暗模式的响应,所以这里用到ModernWpfUI包。
https://www.nuget.org/packages/ModernWpfUI
dotnet add package ModernWpfUI
注意,只有改了前面的TargetFramework为net5.0-windows10.0.18362.0,这里的依赖项才是干净的,否则你会看到ModernWpfUI下面还有一个Microsoft.Windows.SDK.Contracts,这也是WinRT不被支持的根源。接下来,我们需要在App.xaml中引入ModernWpfUI的样式资源。<Application x:Class="demoForWpfCoreModernUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:demoForWpfCoreModernUI"
StartupUri="MainWindow.xaml"
xmlns:ui="http://schemas.modernwpf.com/2019">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources />
<ui:XamlControlsResources />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
</ResourceDictionary>
</Application.Resources>
</Application>
d. 在窗体中引入Mica的Dwm支持据说,微软偷偷的在DWMWINDOWATTRIBUTE加了枚举值,这里面我们主要是利用DWMWA_USE_IMMERSIVE_DARK_MODE和DWMWA_MICA_EFFECT这两个来实现今天的Mica效果。enum DWMWINDOWATTRIBUTE
{
DWMWA_NCRENDERING_ENABLED = 1,
// [get] Is non-client rendering enabled/disabled
[...]
+
DWMWA_USE_HOSTBACKDROPBRUSH,
// [set] BOOL, Allows the use of host backdrop brushes for the window.
+
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
// [set] BOOL, Allows a window to either use the accent color, or dark, according to the user Color Mode preferences.
+
DWMWA_WINDOW_CORNER_PREFERENCE = 33,
// [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners
+
DWMWA_BORDER_COLOR,
// [set] COLORREF, The color of the thin border around a top-level window
+
DWMWA_CAPTION_COLOR,
// [set] COLORREF, The color of the caption
+
DWMWA_TEXT_COLOR,
// [set] COLORREF, The color of the caption text
+
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
// [get] UINT, width of the visible border around a thick frame window
[...]
+
DWMWA_MICA_EFFECT = 1029,
// [set] BOOL, undocumented??
DWMWA_LAST
};
前往MainWindow.xaml.cs文件,新增如下部分:namespace demoForWpfCoreModernUI
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// Get PresentationSource
PresentationSource presentationSource = PresentationSource.FromVisual((Visual)sender);
// Subscribe to PresentationSource's ContentRendered event
presentationSource.ContentRendered += Window_ContentRendered;
}
[DllImport("dwmapi.dll")]
public static extern int DwmSetWindowAttribute(IntPtr hwnd, DwmWindowAttribute dwAttribute, ref int pvAttribute, int cbAttribute);
[Flags]
public enum DwmWindowAttribute : uint
{
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
DWMWA_MICA_EFFECT = 1029
}
// Enable Mica on the given HWND.
public static void EnableMica(HwndSource source, bool darkThemeEnabled)
{
int trueValue = 0x01;
int falseValue = 0x00;
// Set dark mode before applying the material, otherwise you'll get an ugly flash when displaying the window.
if (darkThemeEnabled)
DwmSetWindowAttribute(source.Handle, DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, ref trueValue, Marshal.SizeOf(typeof(int)));
else
DwmSetWindowAttribute(source.Handle, DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, ref falseValue, Marshal.SizeOf(typeof(int)));
DwmSetWindowAttribute(source.Handle, DwmWindowAttribute.DWMWA_MICA_EFFECT, ref trueValue, Marshal.SizeOf(typeof(int)));
}
public static void UpdateStyleAttributes(HwndSource hwnd)
{
// You can avoid using ModernWpf here and just rely on Win32 APIs or registry parsing if you want to.
var darkThemeEnabled = ModernWpf.ThemeManager.Current.ActualApplicationTheme == ModernWpf.ApplicationTheme.Dark;
EnableMica(hwnd, darkThemeEnabled);
}
private void Window_ContentRendered(object sender, System.EventArgs e)
{
// Apply Mica brush and ImmersiveDarkMode if needed
UpdateStyleAttributes((HwndSource)sender);
// Hook to Windows theme change to reapply the brushes when needed
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged += (s, ev) => UpdateStyleAttributes((HwndSource)sender);
}
}
}
注意还要添加两个命名空间的引用:using System.Runtime.InteropServices;
using System.Windows.Interop;
e. 重写Window窗体的WindowChrome我们需要在MainWindow.xaml文件中,新增对WindowChrome.WindowChrome的重写。<WindowChrome.WindowChrome>
<WindowChrome
CaptionHeight="20"
ResizeBorderThickness="8"
CornerRadius="0"
GlassFrameThickness="-1"
UseAeroCaptionButtons="True"
/>
</WindowChrome.WindowChrome>
另外为了达到最终效果,我们需要将Window的背景色设置成透明。<Window
x:Class="demoForWpfCoreModernUI.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:demoForWpfCoreModernUI"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800"
Background="Transparent"
>
...
另外,为了让右侧的那些按钮处于正确的位置,我们还可以自定义WindowChrome中的NonClientFrameEdges来修复Wpf的这个bug。
<WindowChrome.WindowChrome>
<WindowChrome
CaptionHeight="20"
ResizeBorderThickness="8"
CornerRadius="0"
GlassFrameThickness="-1"
UseAeroCaptionButtons="True"
NonClientFrameEdges="Bottom,Left,Right"
/>
</WindowChrome.WindowChrome>
f. 运行看看效果效果还行,其实我验证过,那个黑暗模式的下,效果出不来,具体为啥还没弄清楚,总之就是没透。g. 结合前面的WebView2导航加持WinUI项目添加并使用WebView2控件添加WebView2控件由于WinUI3中已经内置了WebView2控件了,所以我们不许额外安装任何包就可以直接使用。我们改造下HelloWinUI3桌面项目的MainWindow.xaml文件。<WebView2
x:Name="WebViewForMain"
Source="https://www.bing.com"
/>
然后先编译一次项目,随后可以启动部署试试,看看运行效果。使用WebView2控件为了更好的展示WebView2的能力,我们直接复制Demo4Window的已有能力好了。目前WinUI控件提供的事件和能力还不够完善,所以部分效果暂时屏蔽和替换了。其中:WebView2的CoreWebView2InitializationCompleted事件需要替换成CoreWebView2Initialized。
public MainWindow()
{
InitializeComponent();
WebViewForMain.NavigationStarting += WebViewForMain_NavigationStarting;
WebViewForMain.NavigationCompleted += WebViewForMain_NavigationCompleted;
//WebViewForMain.KeyDown += WebViewForMain_KeyDown;
WebViewForMain.CoreWebView2Initialized += WebViewForMain_CoreWebView2Initialized;
}
private void WebViewForMain_CoreWebView2Initialized(object? sender, CoreWebView2InitializedEventArgs e)
{
if (e.Exception!=null)
{
WebViewForMain.CoreWebView2.ProcessFailed += CoreWebView2_ProcessFailed;
}
else
{
//MessageBox.Show($"WebView2创建失败,发生异常 = {e.InitializationException}");
}
}
Border的MouseDown事件需要替换成Tapped。
/// <summary>
/// 导航栏-后退按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviBack_Tapped(object sender, TappedRoutedEventArgs e)
{
#region BorderForNaviBack_MouseDown
if (WebViewForMain.CanGoBack)
{
WebViewForMain.GoBack();
}
else
{
UpdateNaviButtonStatus();
}
#endregion
}
Border的MouseEnter和MouseLeave需要替换成PointerEntered、PointerMoved。
private void BorderForButton_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var border = sender as Border;
border.Background = new SolidColorBrush(Colors.White);
border.Focus(FocusState.Pointer);
}
private void BorderForButton_PointerMoved(object sender, PointerRoutedEventArgs e)
{
var border = sender as Border;
border.Background = new SolidColorBrush(Colors.Transparent);
border.Focus(FocusState.Pointer);
}
WebView2的Stop方法需要替换成Close方法
/// <summary>
/// 导航栏-停止按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviStop_Tapped(object sender, TappedRoutedEventArgs e)
{
#region BorderForNaviStop_MouseDown
WebViewForMain.Close();
UpdateNaviButtonStatus();
#endregion
}
设置部署图标在WinUi的打包项目中,我们可以选中一张高清图作为图标的素材来源,一键生成。让程序拥有自定义图标下载安装图标提取工具IconViewer这里我们去提取一个来用,这里需要用到一个工具,叫IconViewer。
https://www.botproductions.com/iconview/download.html
安装地址:IconViewer3.02-Setup-x64.exe安装之后,啥动静也没有,但是实际已经有用了。使用图标提取工具IconViewer提取图标我们找到我们要提取的目标exe,嗯嗯,肯定是带图标的那个,我们就要提取他的图标哈。选中它,然后右键属性。如果安装顺利,这里会多出一个Icons的标签,我们切过去,哈哈,惊喜来了,这里显示了它的图标,我们还可以选图标的大小,毫无疑问,选最大的那个,点击那个保存按钮就可以了。接下来,我们就顺利得到一个超高清的Ico图标了。给应用程序挂载图标在项目上右键,打开项目"属性",然后找到"图标和清单"部分,浏览我们刚刚保存那个图标即可。运行一看,哈哈,已经生效了。很香吧。理解WebView2的导航事件
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/navigation-events
在网页导航期间,WebView2控件将引发事件。承载WebView2控件的应用侦听以下事件。NavigationStarting
SourceChanged
ContentLoading
HistoryChanged
NavigationCompleted
发生错误时,将引发以下事件,并可能依赖于导航到错误网页。SourceChanged
ContentLoading
HistoryChanged
如果发生HTTP重定向,则一行NavigationStarting中有多个事件。从NavigationStarting事件切入强制HTTPSpublic Demo2Window()
{
InitializeComponent();
WebViewForMain.NavigationStarting += WebViewForMain_NavigationStarting;
}
private void WebViewForMain_NavigationStarting(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
{
if (!e.Uri.ToLower().StartsWith("https://"))
{
e.Cancel = true;
}
}
在Demo2Window窗体构造函数中注册WebViewForMain控件的"导航开始(NavigationStarting)"事件,在WebViewForMain_NavigationStarting事件处理函数中,如果检测到Uri不是以Https开头的,直接取消掉当前导航动作,以达到强制HTTPS的目的。从NavigationCompleted事件切入更新地址栏public Demo2Window()
{
InitializeComponent();
WebViewForMain.NavigationCompleted += WebViewForMain_NavigationCompleted;
}
private void WebViewForMain_NavigationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
TextBoxForSource.Text = WebViewForMain.Source?.ToString();
}
}
在Demo2Window窗体构造函数中注册WebViewForMain控件的"导航完成(NavigationCompleted)"事件,在WebViewForMain_NavigationCompleted事件处理函数中,如果NavigationCompletedEventArgs事件参数是成功状态,那么将当前WebView实例的源地址更新到地址输入框中。从首页点击页面内的链接,跳转到其他页面之后,地址栏也会同步更新,显示当前地址。给页面加载过程增加进度提示有了前面的"导航开始(NavigationStarting)"事件和"导航完成(NavigationCompleted)"事件加持,我们便可以基于它们,提示用户正在加载了。a. 添加一个进度指示器控件ProgressBar<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
...
<ProgressBar
Grid.Row="0"
x:Name="GirdForProgress"
Height="2"
VerticalAlignment="Bottom"
IsEnabled="False"
IsIndeterminate="False"
/>
...
</Grid>
b. 引入一个窗体变量控制进度指示器控件private bool _isNavigationProgress;
public bool IsNavigationProgress
{
get
{
return _isNavigationProgress;
}
set
{
_isNavigationProgress = value;
GirdForProgress.IsEnabled = value;
GirdForProgress.IsIndeterminate = value;
GirdForProgress.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}
}
在IsNavigationProgress变量的Set操作中,我们同步控制GirdForProgress的IsEnabled属性、IsIndeterminate属性、Visibility属性。c. 基于事件控制IsNavigationProgress变量值private void WebViewForMain_NavigationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
TextBoxForSource.Text = WebViewForMain.Source?.ToString();
}
IsNavigationProgress = false;
}
private void WebViewForMain_NavigationStarting(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
{
var uri = e.Uri;
if (!uri.ToLower().StartsWith("https://"))
{
WebViewForMain.CoreWebView2.ExecuteScriptAsync($"alert('{uri} 不安全,请使用HTTPS地址重新访问!')");
e.Cancel = true;
}
IsNavigationProgress = true;
}
在Demo4Window窗体的WebViewForMain_NavigationCompleted事件和WebViewForMain_NavigationStarting事件中分别控制IsNavigationProgress变量值,间接的实现对GirdForProgress展示效果的控制。d. 运行演示效果从KeyDown事件切入支持组合快捷键public Demo4Window()
{
InitializeComponent();
WebViewForMain.KeyDown += WebViewForMain_KeyDown;
}
private void WebViewForMain_KeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat) return;
bool ctrl = e.KeyboardDevice.IsKeyDown(Key.LeftCtrl)
e.KeyboardDevice.IsKeyDown(Key.RightCtrl);
bool alt = e.KeyboardDevice.IsKeyDown(Key.LeftAlt)
e.KeyboardDevice.IsKeyDown(Key.RightAlt);
bool shift = e.KeyboardDevice.IsKeyDown(Key.LeftShift)
e.KeyboardDevice.IsKeyDown(Key.RightShift);
if (e.Key == Key.N && ctrl && !alt && !shift)
{
new MainWindow().Show();
e.Handled = true;
}
else if (e.Key == Key.W && ctrl && !alt && !shift)
{
Close();
e.Handled = true;
}
}
在Demo4Window窗体构造函数中注册WebViewForMain控件的"按键按下(KeyDown)"事件,在WebViewForMain_KeyDown事件处理函数中,如果KeyEventArgs事件参数中是Ctrl+N的组合,那么就新建一个窗口,如果是Ctrl+W的组合,那么就关闭当前窗口,这个快捷键和目前Microsoft Edge是一致的。从CoreWebView2InitializationCompleted事件切入知晓浏览器控件加载完毕public Demo4Window()
{
InitializeComponent();
WebViewForMain.CoreWebView2InitializationCompleted += WebViewForMain_CoreWebView2InitializationCompleted;
}
private void WebViewForMain_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
}
else
{
MessageBox.Show($"WebView2创建失败,发生异常 = {e.InitializationException}");
}
}
在Demo4Window窗体构造函数中注册WebViewForMain控件的"核心初始化完成(CoreWebView2InitializationCompleted)"事件,在WebViewForMain_CoreWebView2InitializationCompleted事件处理函数中,如果CoreWebView2InitializationCompletedEventArgs事件参数中IsSuccess为True,说明浏览器核心初始化成功,如果为False,则表示发生异常情况,那么可以弹出相关提示来告知用户,异常信息通过InitializationException获取。实践WebView2的双向通信从ExecuteScriptAsync方法运行自定义Javascript代码public Demo2Window()
{
InitializeComponent();
WebViewForMain.NavigationStarting += WebViewForMain_NavigationStarting;
}
private void WebViewForMain_NavigationStarting(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
{
if (!e.Uri.ToLower().StartsWith("https://"))
{
WebViewForMain.CoreWebView2.ExecuteScriptAsync($"alert('{uri} 不安全,请使用HTTPS地址重新访问!')");
e.Cancel = true;
}
}
在前面说到的WebViewForMain_NavigationStarting事件处理函数中,我们给强制HTTPS增加一个提示,这里我们需要让WebView替代我们执行一段Javascript代码的警告,以便给用户一个具体的提示,通过WebView实例的ExecuteScriptAsync方法,可以传入自定义的Javascript代码进行执行。从EnsureCoreWebView2Async方法等待WebView2异步加载完成public Demo3Window()
{
InitializeComponent();
InitializeAsync();
}
async void InitializeAsync()
{
// 确保WebView对象已经初始化完成
await WebViewForMain.EnsureCoreWebView2Async(null);
}
如果你曾尝试在Window窗体构建函数或者Windows的Loaded函数去试图绑定WebView2实例的CoreWebView2对象相关的事件,你可能会遇到Null空值错误,原因是WebView2实例的CoreWebView2对象的初始化是异步加载的,如果我们要监听它的事件,那么需要等待它异步加载完成之后才行,所以这里我们在构造函数中,新增了可支持异步等待的InitializeAsync方法,通过EnsureCoreWebView2Async方法,我们可以确保这一句之后执行的代码是CoreWebView2对象已经初始化成功之后的。从WebMessageReceived方法监听来自WebView的消息async void InitializeAsync()
{
// 确保WebView对象已经初始化完成
await WebViewForMain.EnsureCoreWebView2Async(null);
// 监听来自WebView的消息
WebViewForMain.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
}
private void CoreWebView2_WebMessageReceived(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2WebMessageReceivedEventArgs e)
{
// 试图以String的方式接收消息内容
var messageContent = e.TryGetWebMessageAsString();
// 以系统弹窗的方式展示消息内容
MessageBox.Show(messageContent);
}
在WebView2实例的CoreWebView2对象的EnsureCoreWebView2Async方法之后,我们便可以安全的监听WebMessageReceived事件,在CoreWebView2_WebMessageReceived事件处理函数中,出于安全起见,我们试图以TryGetWebMessageAsString的方法以字符串的格式接收消息内容,并且以系统弹窗MessageBox的方式进行展示,这里只是我们临时的一种方案,用于演示哈。从AddScriptToExecuteOnDocumentCreatedAsync方法模拟来自WebView的消息async void InitializeAsync()
{
// 确保WebView对象已经初始化完成
await WebViewForMain.EnsureCoreWebView2Async(null);
// 监听来自WebView的消息
WebViewForMain.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
// 模拟WebView的网站发送消息
await WebViewForMain.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.postMessage(window.document.URL);");
}
有了前面步骤中对WebView2实例的CoreWebView2对象针对WebMessageReceived事件的监听处理之后,我们可能需要模拟一下WebView网站内对客户端的消息动作,以便验证我们的监听处理是否符合预期,通过CoreWebView2对象的AddScriptToExecuteOnDocumentCreatedAsync方法,我们可以在新的网页内容被创建完成后追加一个PostMessage的动作,把当前网页的地址发送给客户端。从AddScriptToExecuteOnDocumentCreatedAsync方法模拟WebView网站监听消息async void InitializeAsync()
{
// 确保WebView对象已经初始化完成
await WebViewForMain.EnsureCoreWebView2Async(null);
// 模拟WebView的网站监听消息
await WebViewForMain.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("window.chrome.webview.addEventListener(\'message\', event => alert(event.data));");
}
前面我们模拟了从WebView网站发送消息,那么反过来,我们也需要模拟下网站监听来自客户端的消息,以便后续响应我们从客户端发送消息给网站。通过CoreWebView2对象的AddScriptToExecuteOnDocumentCreatedAsync方法,我们可以在新的网页内容被创建完成后追加一个AddEventListener的动作,监听来自客户端的消息,并且以警告弹窗的形式把消息内容展示出来。具体效果,稍后将进行验证。从PostWebMessageAsString方法向WebView网站发送消息a. 新增消息发送面板<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
...
<Grid
Grid.Row="1"
x:Name="GirdForMessage"
Margin="8,4"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border
x:Name="BorderForMessage"
CornerRadius="4"
Grid.Column="0"
Height="44"
BorderBrush="Gray"
BorderThickness="1"
Padding="4"
>
<TextBox
x:Name="TextBoxForMessage"
BorderThickness="0"
TextAlignment="Left"
TextWrapping="NoWrap"
Padding="0,6,0,4"
Text=""
FontSize="18"
KeyDown="TextBoxForMessage_KeyDown"
/>
</Border>
<Border
x:Name="BorderForPost"
CornerRadius="4"
Grid.Column="2"
BorderBrush="#0780d8"
BorderThickness="1"
Background="#39baf4"
Padding="4"
MouseDown="BorderForPost_MouseDown"
>
<TextBlock
x:Name="TextBlockForPost"
Text="发送"
Width="100"
FontSize="18"
Background="Transparent"
Foreground="White"
TextAlignment="Center"
VerticalAlignment="Center"
/>
</Border>
</Grid>
...
</Grid>
为了更加可视化的模拟向WebView网站发送消息,并定制消息内容,我们引入一个新的发送消息的面板,在原来的导航面板和WebView控件之间,采用Gird布局,引入"消息输入框(TextBoxForMessage)"和"发送按钮(TextBlockForPost)",在风格上就完全参考之间的导航面板了。b. 响应定制化消息发送private void BorderForPost_MouseDown(object sender, MouseButtonEventArgs e)
{
var messageContext = TextBoxForMessage.Text?.Trim();
WebViewForMain.CoreWebView2.PostWebMessageAsString(messageContext);
}
在"发送按钮(TextBlockForPost)"的响应事件BorderForPost_MouseDown中,通过CoreWebView2对象的PostWebMessageAsString方法,我们可以将界面上的定制化消息发送到网站,如果网站能接收到的话,那么根据前面的监听机制,会弹出包含消息内容的警示弹窗,根据我们的设计,稍作注意是,需要重新加载新网页才能响应。从AddHostObjectToScript方法公开被Javascript调用的本机方法
https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addhostobjecttoscript?view=webview2-dotnet-1.0.992.28
为了更方便的实现JS和本机之间的通信,我们还可以把本地方法通过AddHostObjectToScript方法暴漏给Web来实现调用,这等同于传统WebBrower控件的ObjectForScripting方法实现。a. 对WebView2进行一些安全设置,允许使用注入本机方法等待CoreWebView2核心初始化完毕之后,我们应该尽快完成一些安全设置,允许使用注入本机方法。private async void Demo5Window_Loaded(object sender, RoutedEventArgs e)
{
await WebViewForMain.EnsureCoreWebView2Async();
WebViewForMain.CoreWebView2.Settings.AreHostObjectsAllowed = true;
WebViewForMain.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
WebViewForMain.CoreWebView2.Settings.IsScriptEnabled = true;
WebViewForMain.CoreWebView2.Settings.IsWebMessageEnabled = true;
}
b. 定义公开的本机方法类#pragma warning disable CS0618
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDual)]
#pragma warning restore CS0618
[System.Runtime.InteropServices.ComVisible(true)]
public class C2WHostObject
{
public void ClientFunction(string requestInfo)
{
Console.WriteLine(requestInfo);
}
public string ClientValueBack(string requestInfo)
{
return requestInfo;
}
}
这里对需要公开的本机方法类,需要通过System.Runtime.InteropServices.ComVisible(true)和System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDual)来公开它,否则将不可见。
这里由于ClassInterfaceType.AutoDual即将被废弃,暂时先通过#pragma warning disable CS0618关闭警告,.NET host objects need to use deprecated AutoDual attribute
c. 等待CoreWebView2核心初始化完毕之后,注册本机公开方法private async void Demo5Window_Loaded(object sender, RoutedEventArgs e)
{
await WebViewForMain.EnsureCoreWebView2Async();
WebViewForMain.CoreWebView2.AddHostObjectToScript("webView2Bridge", new C2WHostObject());
}
这里需要给这个公开方法对象取个名称,这里我们暂时叫它:webView2Bridge。d. 在WebView2中F12进入DevTool尝试调用await chrome.webview.hostObjects.webView2Bridge.ClientFunction("somethings");
await chrome.webview.hostObjects.webView2Bridge.ClientValueBack("somethings");
处理WebView2的异常机制从ProcessFailed事件切入监听浏览器异常private void WebViewForMain_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
WebViewForMain.CoreWebView2.ProcessFailed += CoreWebView2_ProcessFailed;
}
}
private void CoreWebView2_ProcessFailed(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2ProcessFailedEventArgs e)
{
switch (e.ProcessFailedKind)
{
// 浏览器进程退出
case CoreWebView2ProcessFailedKind.BrowserProcessExited:
{
}
break;
// 浏览器渲染进程未响应
case CoreWebView2ProcessFailedKind.RenderProcessUnresponsive:
{
}
break;
// 浏览器渲染进程退出
case CoreWebView2ProcessFailedKind.RenderProcessExited:
{
}
break;
// 框架渲染进程退出
case CoreWebView2ProcessFailedKind.FrameRenderProcessExited:
{
}
break;
default:
{
// Show the process failure details. Apps can collect info for their logging purposes.
StringBuilder messageBuilder = new StringBuilder();
messageBuilder.AppendLine($"Process kind: {e.ProcessFailedKind}");
messageBuilder.AppendLine($"Reason: {e.Reason}");
messageBuilder.AppendLine($"Exit code: {e.ExitCode}");
messageBuilder.AppendLine($"Process description: {e.ProcessDescription}");
System.Threading.SynchronizationContext.Current.Post((_) =>
{
MessageBox.Show(messageBuilder.ToString(), "Child process failed", MessageBoxButton.OK);
}, null);
}
break;
}
}
在在Demo4Window窗体的"核心初始化完成(CoreWebView2InitializationCompleted)"事件响应中,通过注册CoreWebView2对象的"进程失败(ProcessFailed)"事件,在CoreWebView2_ProcessFailed事件处理函数中,可通过e.ProcessFailedKind来根据进程失败的种类分情况灵活处理。进一步的处理细节参考:https://github.com/MicrosoftEdge/WebView2Samples/blob/c7d7c75184dec0c46634f27a8f4beba320b04618/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs#L223优化WebView2导航控制引入Segoe Fluent Icons字体图标今天我们引入一个Windows 11最新版的图标字体Segoe Fluent Icons,如果想要查看字体内图标清单,可以浏览:https://linrstudio.github.io/win11/SEGOEICONS.html 查阅。而要在WPF中引入字体,并且使用,我们先把下载好的字体丢进项目下Fonts目录。记得将字体文件设置成"始终复制"和生成操作为"内容"。稍后在TextBlock中写FontFamily使用/MiniEdge;component/Fonts/#Segoe Fluent Icons,其中MiniEdge是程序集的命名空间,Fonts是字体文件的路径,而Segoe Fluent Icons是字体名称。字体名称建议你双击.ttf打开看一下。而在TextBlock中的Text需要采用&#开头和;结尾的编码,比如:&#57606;<TextBlock
x:Name="TextBlockForNaviStop"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57606;"
FontSize="26"
VerticalAlignment="Center"
Foreground="Black"
/>
构建更丰富的导航控制面板a. 引入后退、前进、刷新、停止、主页按钮布局<Grid Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border
x:Name="BorderForNaviBack"
CornerRadius="4"
Grid.Column="1"
Padding="16,4"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
MouseDown="BorderForNaviBack_MouseDown"
>
<TextBlock
x:Name="TextBlockForNaviBack"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57618;"
FontSize="24"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
<Border
x:Name="BorderForNaviForward"
CornerRadius="4"
Grid.Column="3"
Padding="16,4"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
MouseDown="BorderForNaviForward_MouseDown"
>
<TextBlock
x:Name="TextBlockForNaviForward"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57515;"
FontSize="24"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
<Grid Grid.Column="5">
<Border
x:Name="BorderForNaviStop"
CornerRadius="4"
Grid.Column="5"
Padding="16,4"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
MouseDown="BorderForNaviStop_MouseDown"
Visibility="Collapsed"
>
<TextBlock
x:Name="TextBlockForNaviStop"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57606;"
FontSize="26"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
<Border
x:Name="BorderForNaviRefresh"
CornerRadius="4"
Grid.Column="5"
Padding="16,4"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
MouseDown="BorderForNaviRefresh_MouseDown"
>
<TextBlock
x:Name="TextBlockForNaviRefresh"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57673;"
FontSize="24"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
</Grid>
<Border
x:Name="BorderForNaviHome"
CornerRadius="4"
Grid.Column="7"
Padding="16,4"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
MouseDown="BorderForNaviHome_MouseDown"
>
<TextBlock
x:Name="TextBlockForNaviHome"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57615;"
FontSize="24"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
</Grid>
b. 响应后退、前进、刷新、停止、主页按钮动作#region NaviButton
/// <summary>
/// 导航栏-后退按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviBack_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviBack_MouseDown
if (WebViewForMain.CanGoBack)
{
WebViewForMain.GoBack();
}
else
{
UpdateNaviButtonStatus();
}
#endregion
}
/// <summary>
/// 导航栏-前进按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviForward_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviForward_MouseDown
if (WebViewForMain.CanGoForward)
{
WebViewForMain.GoForward();
}
else
{
UpdateNaviButtonStatus();
}
#endregion
}
/// <summary>
/// 导航栏-主页按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviHome_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviHome_MouseDown
WebViewForMain.CoreWebView2.Navigate("https://www.bing.com");
UpdateNaviButtonStatus();
#endregion
}
/// <summary>
/// 导航栏-刷新按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviRefresh_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviRefresh_MouseDown
WebViewForMain.Reload();
UpdateNaviButtonStatus();
#endregion
}
/// <summary>
/// 导航栏-停止按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviStop_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviStop_MouseDown
WebViewForMain.Stop();
UpdateNaviButtonStatus();
#endregion
}
#endregion
这里比较简单,主要是调用WebView2实例的GoBack()、GoForward()、Reload()、Stop()函数来完成对应的功能。/// <summary>
/// 更新导航栏-按钮-状态
/// </summary>
private void UpdateNaviButtonStatus()
{
#region UpdateNaviButtonStatus
var isCanGoBack = WebViewForMain.CanGoBack;
BorderForNaviBack.IsEnabled = isCanGoBack;
TextBlockForNaviBack.Foreground = isCanGoBack ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.Gray);
var isCanGoForward = WebViewForMain.CanGoForward;
BorderForNaviForward.IsEnabled = isCanGoForward;
TextBlockForNaviForward.Foreground = isCanGoForward ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.Gray);
#endregion
}
同时,为了当前后导航不可用的时候,能给用户一个明确提示,我们将其禁用并且颜色置灰。回到主页按钮,暂时用CoreWebView2对象的Navigate方法来实现,其实我理解点击主页之后,应要清空前后导航的,但是还没找到对应的方法来做这件事。c. 优化后退、前进、刷新、停止、主页按钮交互private void BorderForButton_MouseEnter(object sender, MouseEventArgs e)
{
var border = sender as Border;
if (border.IsEnabled)
{
border.Background = new SolidColorBrush(Colors.White);
border.Focus();
}
}
private void BorderForButton_MouseLeave(object sender, MouseEventArgs e)
{
var border = sender as Border;
if (border.IsEnabled)
{
border.Background = new SolidColorBrush(Colors.Transparent);
}
}
实际上,我们给所有的图标按钮标配了一个效果,就是鼠标移上去就背景变白,移开后恢复,这样交互更加明确。对于刷新和停止按钮,我们还需要根据是否正在加载来切换他们的显影,那么在之前的IsNavigationProgress中处理就好了。private bool _isNavigationProgress;
public bool IsNavigationProgress
{
get
{
return _isNavigationProgress;
}
set
{
_isNavigationProgress = value;
GirdForProgress.IsEnabled = value;
GirdForProgress.IsIndeterminate = value;
GirdForProgress.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
BorderForNaviRefresh.IsEnabled = !value;
TextBlockForNaviRefresh.Foreground = !value ? new SolidColorBrush(Colors.Black) : new SolidColorBrush(Colors.Gray);
BorderForNaviStop.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
BorderForNaviRefresh.Visibility = !value ? Visibility.Visible : Visibility.Collapsed;
}
}
d. 优化导航按钮为图标按钮,统一交互和视觉<Grid Grid.Column="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<Border
x:Name="BorderForNaviTarget"
CornerRadius="4"
Grid.Column="0"
Padding="16,4"
MouseDown="BorderForNaviTarget_MouseDown"
MouseEnter="BorderForButton_MouseEnter"
MouseLeave="BorderForButton_MouseLeave"
>
<TextBlock
x:Name="TextBlockForNaviTarget"
FontFamily="/MiniEdge;component/Fonts/#Segoe Fluent Icons"
Text="&#57763;"
FontSize="24"
VerticalAlignment="Center"
Foreground="Black"
/>
</Border>
</Grid>
/// <summary>
/// 导航栏-指定按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviTarget_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviTarget_MouseDown
#endregion
}
/// <summary>
/// 导航栏-地址输入框-快捷键(回车)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextBoxForNaviAddress_KeyDown(object sender, KeyEventArgs e)
{
#region TextBoxForNaviAddress_KeyDown
if (e.Key == Key.Enter)
{
BorderForNaviTarget_MouseDown(null, null);
}
#endregion
}
e. 优化地址输入框交互和视觉体验<Grid Grid.Column="2">
<Border
x:Name="BorderForNaviAddress"
CornerRadius="4"
Grid.Column="2"
Height="44"
BorderBrush="Gray"
BorderThickness="1"
Padding="4"
Background="White"
>
</Border>
<TextBox
Margin="4"
x:Name="TextBoxForNaviAddress"
BorderThickness="0"
TextAlignment="Left"
TextWrapping="NoWrap"
Padding="0,6,0,4"
Text=""
FontSize="18"
KeyDown="TextBoxForNaviAddress_KeyDown"
MouseEnter="TextBoxForNaviAddress_MouseEnter"
MouseLeave="TextBoxForNaviAddress_MouseLeave"
Background="White"
Foreground="Black"
/>
</Grid>
我们做了一个布局调整,将Border和TextBox平行放在一个Gird里面,这样布局的好处就是当Border样式改变的时候,不会影响到TextBox。private void TextBoxForNaviAddress_MouseEnter(object sender, MouseEventArgs e)
{
BorderForNaviAddress.BorderBrush = new SolidColorBrush(Color.FromRgb(143, 177, 229));
BorderForNaviAddress.BorderThickness = new Thickness(1.5);
}
private void TextBoxForNaviAddress_MouseLeave(object sender, MouseEventArgs e)
{
BorderForNaviAddress.BorderBrush = new SolidColorBrush(Colors.Gray);
BorderForNaviAddress.BorderThickness = new Thickness(1);
}
接下来,当然输入框被鼠标靠近的时候,我们让输入框背后的背景边框变个颜色,并且加粗边框,被鼠标移开的时候,效果还原。f. 优化地址输入框直达和搜索体验有时候用户可能输入的是一个网址链接,或者是一个不带HTTP头的链接,亦或只是联想的一些关键词,那么我们分开处理,以确保得到最佳体验。/// <summary>
/// 导航栏-指定按钮-点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BorderForNaviTarget_MouseDown(object sender, MouseButtonEventArgs e)
{
#region BorderForNaviTarget_MouseDown
var sourceContent = TextBoxForNaviAddress.Text?.Trim() ?? string.Empty;
if (!string.IsNullOrEmpty(sourceContent))
{
Uri? sourceUri;
// 如果当前地址是格式化合规的地址,那么直接使用
if (Uri.IsWellFormedUriString(sourceContent, UriKind.Absolute))
{
sourceUri = new Uri(sourceContent);
}
// 如果当前地址含.符号切不含空格,那么自动追加前缀
else if (!sourceContent.Contains(" ") && sourceContent.Contains("."))
{
sourceUri = new Uri("http://" + sourceContent);
}
// 如果当前地址不属于上诉情况,那么通过内置搜索引擎搜索
else
{
var searchKeywords = string.Join("+", Uri.EscapeDataString(sourceContent).Split(new string[] { "%20" }, StringSplitOptions.RemoveEmptyEntries));
var bingSearchAddress = $"https://bing.com/search?q={searchKeywords}";
sourceUri = new Uri(bingSearchAddress);
}
if (sourceUri != null)
{
WebViewForMain.CoreWebView2.Navigate(sourceUri.ToString());
}
}
#endregion
}
g. 不如取个更好的名字吧之前我们管它叫WebView2浏览器,该给它取个正式的名称了,不如就叫MiniEdge吧,毕竟是借鉴了Edge的交互和视觉,还复用了它的渲染引擎。需要注意的是,我们同时也把程序集名称一起改了。这样最终exe就会改名字了。注意也要把Demo4Window.xaml的Title改了。WebView2的最佳部署指南
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/distribution#deploying-the-evergreen-webview2-runtime
WebView2的最佳开发指南每个开发团队在构建其应用程序时都遵循不同的做法。生成WebView2生产应用时,建议遵循这些建议和最佳做法。使用EvergreenRuntime(推荐)我们通常建议使用"Evergreen WebView2运行时"。固定版本运行时分发仅建议用于具有严格兼容性要求的应用。"Evergreen运行时"在客户端上自动更新,以便你的WebView2应用可以使用最新的功能和安全修补程序。与固定版本运行时相比,"Evergreen运行时"还需要更少的磁盘上的存储空间。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/distribution#deploying-the-evergreen-webview2-runtime
如果使用"Evergreen运行时",在运行WebView2应用之前,测试是否已在客户端上安装"Evergreen WebView2运行时"。使用Evergreen运行时时定期运行兼容性测试
https://www.microsoftedgeinsider.com/download
使用"Evergreen WebView2运行时"时,运行时会自动更新,因此必须定期运行兼容性测试。若要确保WebView2应用继续正常工作,请针对Microsoft Edge Insider(preview)Channels(Beta、Dev或Canary)在WebView2控件中测试Web内容。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/distribution#test-your-app-for-forward-compatibility
本指南类似于我们向Web开发人员提供的指导。测试安装的WebView2运行时是否支持较新的API若要运行使用Webview2 SDK的特定版本开发的WebView2应用,客户端必须已安装WebView2运行时的兼容版本。由于API不断添加到WebView2,因此也发布了新版本的运行时以支持新的API。使用功能检测确保安装在客户端上的WebView2运行时支持WebView2应用使用的较新的API。如果使用"Evergreen WebView2运行时",在某些情况下,客户端上的运行时尚未自动更新到最新版本。例如,如果客户端没有Internet访问权限,则运行时不会自动更新。此外,某些组策略会暂停运行时的更新。将更新推送到WebView2应用时,如果应用尝试调用客户端安装运行时中不可用的较新API,该应用可能无法运行。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/versioning#feature-detecting-to-test-whether-the-installed-runtime-supports-recently-added-apis
若要解决此问题,在代码调用最近添加的WebView2 API之前,测试该API在客户端的安装运行时中是否可用。此较新功能测试与其他Web开发最佳实践类似,这些最佳实践在使用新的WebAPI之前检测支持的功能。若要测试已安装运行时中的API可用性,请使用:QueryInterface在C/C++中。
try/catch .NET或WinUI中的块。
更新固定版本运行时
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/distribution#details-about-the-fixed-version-runtime-distribution-mode
如果使用固定版本的"WebView2运行时",请确保定期更新与应用打包的"WebView2运行时",以减少安全风险。在Webview2应用中使用第三方内容时,始终考虑不受信任的内容。管理新版本的EvergreenRuntime将新版本的"Evergreen WebView2运行时"下载到客户端后,正在运行的任何WebView2应用将继续使用早期版本的运行时,直到发布浏览器进程。此行为允许应用连续运行,并阻止删除以前的运行时。若要使用新版本的运行时,需要释放对以前的WebView2环境对象的所有引用,或重新启动应用。下次应用创建新的WebView2环境时,应用将使用新版本的运行时。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/reference/win32/icorewebview2environment#add_newbrowserversionavailable
https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.web.webview2.core.corewebview2environment.newbrowserversionavailable
当新版本的运行时可用时,你的应用可以自动采取措施,例如通知用户重新启动该应用。若要检测新版本的运行时是否可用,可以在代码中使用add_NewBrowserVersionAvailable(Win32)或CoreWebView2Environment.NewBrowserVersionAvailable(.NET)事件。如果你的代码处理重新启动应用,请考虑在WebView2应用退出之前保存用户状态。管理用户数据文件夹的生命周期
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/user-data-folder
WebView2应用创建用户数据文件夹来存储Cookie、凭据和权限等数据。创建文件夹后,应用负责管理用户数据文件夹的生命周期。例如,卸载应用时,你的应用必须执行清理操作。处理运行时进程故障WebView2应用应侦听和处理事件,以便该应用可以从支持WebView2应用进程的运行时进程故障ProcessFailed中恢复。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/reference/win32/icorewebview2processfailedeventargs
与应用进程一起运行的运行时进程集合支持WebView2应用。这些支持运行时进程可能由于各种原因(如内存不足或用户终止)而失败。当支持运行时进程失败时,WebView2将通过引发ProcessFailed事件通知应用。遵循建议的WebView2安全性最佳做法
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/security
对于任何WebView2应用,请确保遵循我们建议的WebView2安全性最佳做法。WebView2的最佳安全指南
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/concepts/security
WebView2控件允许开发人员在本机应用程序中承载Web内容。正确使用时,承载Web内容具有多项优势,例如使用基于Web的UI、访问Web平台的功能、跨平台共享代码等。
为了避免承载Web内容时可能出现的漏洞,请确保设计WebView2应用程序以密切监视Web内容和主机应用程序之间的交互:
将所有Web内容视为不安全。
使用每个参数之前验证Web消息和主机对象参数,因为Web消息和参数可能格式不正确(无意或恶意)并会导致应用意外运行。
始终检查在WebView2内运行的文档的来源,并评估内容可信度。
设计特定的Web消息和主机对象交互,而不是使用泛型代理。
设置以下选项,通过修改ICoreWebView2Settings(Win32)或CoreWebView2Settings(.NET)来限制Web内容。
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/reference/win32/icorewebview2settings
https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.web.webview2.core.corewebview2settings
如果您不期望Web内容访问主机对象,则设置AreHostObjectsAllowed为false。
如果预计Web内容不会向本机应用程序发布Web消息,则设置IsWebMessageEnabled为false。
如果您不期望Web内容运行脚本,则设置IsScriptEnabled为false(例如,当显示静态html content)。
如果您预计Web内容不会显示或对话框AreDefaultScriptDialogsEnabled为false。
在以下步骤中,使用NavigationStarting和FrameNavigationStarting事件根据新页面的来源更新设置。
若要阻止应用程序导航到特定页面,请使用事件检查然后阻止页面或框架导航。
导航到新页面时,你可能需要调整ICoreWebView2Settings(Win32)或CoreWebView2Settings(.NET)上的属性值,如前面所述。
导航到新文档时,使用ContentLoading事件删除公开的主机对象RemoveHostObjectFromScript。
参考WebView2简单试用(七)—— WebMessage
基于 Chromium Edge ,微软发布 WebView2 四项更新:Windows App SDK、WinUI2(UWP)、Win11 内置 WebView2 Runtime
Microsoft EdgeWebView2 和 Microsoft 365 应用版
WebView2 Windows App SDK, WinUI2, Runtime, and CDP Helper Updates
.Net桌面端开发使用WebView2,可以放弃CefSharp?
WebView2 使用及现状
Microsoft Edge WebView2初体验
C#使用Microsoft Edge WebView2记录
【WPF实用教程1】WPF使用Iconfont图标字体
https://github.com/MicrosoftEdge/WebView2Samples/blob/master/SampleApps/WebView2WpfBrowser/MainWindow.xaml.cs
SEGOEICONS
iconfont-preview
WebView2 control is missing from Toolbox
WebView2 doesn't appear in Toolbox; NavigationCompleted does not occur on 2nd Navigation
iconfont 在线预览工具及其解析
使用iconfont图标的unicode编码动态赋值,发现只显示编码,不显示图片
C# Winform 中使用字体图标
C#使用Microsoft Edge WebView2记录-C#和JS互相调用
Setting an object from .NET to JavaScript code through WebView2
.NET host objects need to use deprecated AutoDual attribute
Using new WebView2 control and hitting older server with window.external calls... is there any way to capture those?
Is AddScriptToExecuteOnDocumentCreatedAsync meant to execute on cross origin iframes?
Setting an object from .NET to JavaScript code through WebView2
Two way communication between native and JS
C# WEBBROWSER控件与JS互调
c#和javascript函数的相互调用(ObjectForScripting 的类必须对 COM 可见。请确认该对象是公共的,或考虑向您的类添加 ComVisible 属性。)
WebBrowser 类
WebBrowser.ObjectForScripting 属性
https://github.com/Difegue/Mica-WPF-Sample
Apply Mica to a WPF app on Windows 11
The WindowChrome class needs to be updated & fixed #3887
ModernWPF UI Library
Built-in support for WinRT is removed from .NET
}
前言在看这篇文章之前,推荐先看下《卧槽!perl和C语言实现对比,perl代码量太赞了!》,不知道你看了吗2?推荐一些文章:Perl系列文章两个半小时学会PerlPerl 教程本文内容是基于《Perl语言入门·第七版》整理所得,而且章节标题和书籍基本对应。本文食用方式,推荐使用搜索关键字和查看目录的方式快速索引到需要的内容;本文内容全部是手敲的,难免有错误,望各位看客多多包涵!本文涉及到的习题,基本都实现了,并且同步在了Gitee,按需下载第1章 简介《Perl语言入门·第七版》https://www.learning-perl.com/可以下载源代码https://www.cpan.org/是Perl综合典藏网,包含很多基于Perl的内容,如源代码、安装程序、示例文件第2章 标量数据scalar(标量)标量数据:表示数据的内容,就是值;标量变量:表示存储标量数据的容器;数字前置零的写法只用于表示数字直接量,不能用于字符串和数字间的自动转换。字符串养成加上use utf8;的习惯。单引号内的字符直接量单引号字符串中,反斜线表示转义的两种情况:反斜线后面跟反斜线或单引号;双引号内的字符直接量与单引号相比,转义字符的内容更加强大。组合意义\n换行\t水平制表符\r回车\f换页符\b退格\a系统响铃\e跳出(ASCII编码的转义字符)\\反斜线\“双引号\l将下个字母转为小写\L将它后面的所有字符都转为小写,直到\E为止\u将下个字母转为大写\U将它后面的所有字符都转为大写,直到\E为止\E结束\L、\U和\Q开始的作用范围字符串操作符操作符可以使用句点符号.进行拼接!Perl的内置警告信息形式1:#!/usr/bin/perl
use warnings;
形式2:#!/usr/bin/perl -w
形式3:运行时加上 -w选项!use diagnostics;报告核心内容标量变量(variable)和其他语言一样,只是perl的变量需要拿钱买来,形如$fredprint输出结果用say代替print输出结果perl -E 'say q(hello, world)'
[255]
hello, world
字符串中的标量变量内插变量内插有时候又叫双引号内插!比较操作符注记:lt:less thangt:greatereq:equal比较数字字符串相等==eq不等!=ne大于>=lt小于<gt小于或等于<=le大于或等于>=geif控制结构和其他语言一样获取用户输入$line = <STDIN>
chomp操作符取消末尾的换行符chomp函数的返回值是移除的字符数chomp($text = <STDIN>);#读取不带换行符的输入
#等价于
$text = <STDIN>;
chomp($text);
while控制结构与其他语言一样undef值ubdef作为数字、字符串使用时会被视作数字零、空字符串defined函数defined函数:判断某个字符串是否为空,如果是undef,返回假!习题第3章 列表与数组访问数组中的元素注意$fred[0]与fred是两个不同的东西特殊的数组索引如果数组定义如下:$name[0] = 'a';
······
$name[88] = 'asd';
最后一个元素的索引可以是$#name和-1列表直接量(1, 2, 3) #包含三个数字的列表
(1,"fred", 2) #列表内元素存储格式灵活
(1..100) #从1到100的整数序列
qw简写qw表示为quoted word用引号引用的词或 quoted by whitespace用空白引用的词;("fred", "barney", "betty", "wilma", "dino")
# 等价的几种写法,少了很多引号,方便使用!
qw( fred barney betty wilma dino )
qw! fred barney betty wilma dino !
qw{ fred barney betty wilma dino }
qw[ fred barney betty wilma dino ]
qw/ fred barney betty wilma dino /
qw< fred barney betty wilma dino >
qw# fred barney betty wilma dino #
······
列表的赋值pop和push操作符删除和增加数组末尾元素shift和unshift操作符删除和增加数组开头元素splice操作符splice 最多接收4个参数,分别为目标数组,操作数组的开始索引,操作的元素个数,替换的元素字符串中的数组内插注意在使用@符号的时候,在输入邮箱的时候,需要用转义符!foreach控制结构和c++中的增强for循环类似。在这里,perl会有默认变量来接收!Perl最喜欢用的默认变量:$_reverse操作符操作后如果需要更数组内容,就需要用数组去接收,否则不会生效!sort操作符此处的sort还不能很完美的支持数字的排序,因为仅仅根据ascii顺序排序的!标量上下文与列表上下文此处想表达的是,相同的符号在不同的语境下有不同的含义!比如,数字 + 数组 得到的是 数字,数组名与数字进行比较时,数组名返回的是数组的长度在标量上下文中使用产生列表的表达式同样是 reverse数组 操作,接收的类型不同,得到的内容就不同!如果是数组接收,以数组元素为最小单位翻转如果是标量接收,以最小字符为最小单位翻转在列表上下文中使用产生标量的表达式列表上下文中的如果接收的是数组类型,则可以多行输入;习题第4章 子程序前文用到内置的系统函数有chomp,reverse,print和其他语言类似,perl也可以自己定义子程序,子程序名属于独立的命名空间,调用子程序一般需要加上&定义子程序子程序的形式,如下所示:sub marine {
$n += 1; # 全局变量$n
print "Hello, sailor number $n!\n"
}
与c语言相比,perl子程序的定义可以出现在任何位置,也不需要出现函数的声明;如上的子程序,操作的是全局变量,也就是说,前文用到的都是全局变量;调用子程序调用子程序,又可以叫呼叫子程序;子程序名前面加上&就可以调用,如调用上面的子程序可以用&marine;如果学过c/c++,不知道这里是否可以理解为取内存中的地址,然后执行?返回值调用子程序都会有返回值每次写返回值表达式显得费时费力perl的返回值简化为,返回子程序执行过程中最后一次运算的结果小心检查最后的返回值是不是你想要的内容!你晓得吧!参数在调用的传参时候,perl会自动参数劣列表转化为数组变量@_;如下定义的子程序,可以使用&max(10,15);这样的形式进行传参调用!sub max {
if(@_[0] > @_[1]){
$_[0];
} else {
$_[1];
}
}
子程序中的私有变量使用my定义私有变量,如下所示sub max {
my($m, &n);
# 该语句块中的新私有变量
($m, &n) = @_; # 将该参数赋值给变量
if ($m > $n) { $m } else { $n }
# 只有写成一行时,内部的分号才可以省略
}
当然定义和初始化可以放在一行语句内,如下:sub max {
my($m, &n) = @_; # 对子程序的参数命名
if ($m > $n) { $m } else { $n }
# 只有写成一行时,内部的分号才可以省略
}
变长参数列表可以在子程序内部开头判断传入参数的个数,是否符合预期!sub max {
if (@_ != 2) {print "WARNING!应该传入两个参数!"}
my($m, &n) = @_; # 对子程序的参数命名
if ($m > $n) { $m } else { $n }
# 只有写成一行时,内部的分号才可以省略
}
改进版的&max子程序这里其实就是选择比较法,需要理解的就是@_和$_;@_:子程序参数列表$_:foreach循环读到的值#!/usr/bin/perl -w
sub max{
my($max_so_far) = shift @_;
foreach (@_) {
if ($_ > $max_so_far) {
$max_so_far = $_;
}
}
$max_so_far;
}
$maximum = &max(3, 5, 10, 4, 6);
print $maximum;
空列表参数需要注意的是,返回值可能为undef用my声明的词法变量my操作符并不会更改变量赋值时的上下文!my($num) = @_; # 列表上下文,和($num) = @_;相同
my $num
= @_; # 标量上下文,和 $num
= @_;相同
my操作符不加括号时,只能声明靠近的单个词法变量!推荐使用my操作符定义自己的私有变量use strict 编译指令开启这个编译指令,就要求在自定义变量声明前,必须加上myuse strict;:强制使用严格、良好的编程风格use v5.12:自动加载strict编译指令大部分人的建议:比屏幕长的程序都应该加上use strict;return操作符满足条件后,终止子程序!省略与号在调用子程序时,传入参数时,可以完全省略&:编译器能明显感知到调用的是子程序自定义子程序,如果命名与内置函数重名,调用时一定要加上&非标量返回值返回值可以是标量,也可以是列表!持久化私有变量此处引入关键字state,在子程序中,可以一直存在,小编认为是c语言形式的static的修饰,仍然在全局区!子程序签名乍一看,这个特性的加入,使其表现形式越来越像c语言函数的写法!习题第5章 输入与输出读取标准输入行输入操作符:<STDIN>与Perl的默认变量$_之间并无直接关联!comp:截掉最后的换行符defined:可以用来判断是否读取到末尾!下面有两个程序,执行效果相同,但他们的原理真的相同吗?while实现的:while(<STDIN>) {
print "I saw $_";
}
foreach实现的:foreach(<STDIN>) {
print "I saw $_";
}
结果就是,不一样:foreach是先把数据全部读进来;而while是读一行执行一行;如果要处理400MB数据,估计还是选择while好一点!来自钻石操作符的输入钻石操作符<>的名字竟然是Larry的女儿命名的!钻石操作符是行输入操作符的特例,通常会处理所有的输入,所以在程序中一般只出现一次!下面展示两个相同功能的代码!代码1:while(<>){
chomp;
print "It was $_ that I saw!\n";
}
代码2:while(defined($line = <>)){
chomp($line);
print "It was $_ that I saw!\n";
}
双钻石操作符如果命令行传入的文件名中包含==管道符|==等特殊字符,就会引发管道等特殊操作。因此引入<<>>双钻石操作符,功能没变调用参数钻石操作符并不会检查命令行参数,其实也只是把命令行参数存放到@ARGV中因此,可以通过修改@ARGV的内容,强制修改钻石操作符要读取的内容如@ARGV = qw{larry moe curly}; #强制让钻石操作符只读取这三个文件
while(<>){
chomp;
print "It was $_ that I saw in some stooge-link file!\n"
}
输出到标准输出下面两种打印输出有什么区别?print @array;print “@array”;第二行输出的时候,会在元素之间添加空格;行缓冲,也是是用换行符来进行刷新的!print加括号和不加括号的区别,注意!用printf格式化输出这里和c语言的格式高度类似,printf使用后面的括号是选择性的,如果加上,和C语言就一样了!参数宽度也可以作为参数另外指定!如下的格式:#!/usr/bin/perl -w
use strict;
use warnings;
print "Hello world\n";
printf("这里直接是以字符串的格式输出的:\n");
printf "%*s\n", 10, "wilma";
printf("%*s\n", 10, "wilma");
printf("注意,此处参数宽度用参数传入:\n");
printf "%*.*f\n", 6, 2, 3.1415926;
printf("%*.*f\n", 6, 3, 3.1415926);
运行结果如下:Hello world
这里直接是以字符串的格式输出的:
wilma
wilma
注意,此处参数宽度用参数传入:
3.14
3.142
数组和printf有没有发现和python的格式化输出也一样!再一次感受到,编程思想比编程语言本身重要很多!my @items = qw{ wilma dino pebbles };
printf "printf输出添加括号:\n";
printf("The items are:\n".("%10s\n" x @items), @items);
printf "printf输出不加括号:\n";
printf "The items are:\n".("%10s\n" x @items), @items;
运行结果:printf输出添加括号:
The items are:
wilma
dino
pebbles
printf输出不加括号:
The items are:
wilma
dino
pebbles
文件句柄注意联系I/O操作,6个保留的文件句柄:STDIN、STDOUT、STDERR、DATA、ARGV、ARGVOUT打开文件句柄open CONFIG, 'dino';
# 可读可写
open CONFIG, '<dino'; # 只读
open BEDROCK, '>fred';# 可写
open LOG, '>>logfile';# 可追加写
open CONFIG, 'dino';
# 可读可写
open CONFIG, '<', 'dino'; # 只读
open BEDROCK, '>', 'fred';# 可写
open LOG, '>>:encoding(UTF-8)', 'logfile';# 可追加写,还可以指定特定编码
以二进制方式读写文件句柄句柄前加上binmode,直接以二进制数据流的方式读写。即使在二进制文件中碰巧出现内部编码和换行符相同的字符,也不会将其当做文本文件中的换行符来处理。异常文件句柄的处理my $success = open LOG, '>>','logfile'; # 通过返回值可以判断是否成功?
if(!$success){
# open 操作失败
}
关闭文件句柄有打开就要有关闭,close,就如C中,有malloc就要有free;C++中,有new就要有delete;用die处理致命错误die函数的参数是要发出的错误信息文本,一般会输出到标准错误流,同时让程序退出运行!注意下面程序,$!记录的是程序最后返回给操作系统的错误代码!小编怀疑,die也是由linux中系统函数perror封装的,但只是猜测if(! open LOG, '>>', 'logfile') {
die "Cannot create logfile:$!";
}
用warn发出警告信息与die相比,warn不会终止程序的运行!自动检测致命错误在使用的时候,在前面加上use autodie;这种声明,就会自动判断!使用文件句柄有点像c中的sprintfprint LOG "Captain's log, stardate 3.145159\n" #输出到文件句柄 LOG
printf STDERR "%d percent complet.\n", $done/$total * 100;
改换用于输出的默认文件句柄print和printf的默认输出句柄是STDOUT,我们可以使用select BEDROCK;格式来进行切换输出句柄重新打开标准文件句柄在重新打开了 STDERR之后,任何从Perl产生的错误信息都会送到新的文件里。但如果程序执行到die这部分的代码,那会怎样呢?也就是说,如果无法成功打开文件来接收错误信息,那么错误信息会流到哪里去?在重新打开这三个系统文件句柄 STDIN、 STDOUT或 STDERR失败时,Perl会热心地帮你找回原先的文件句柄。也就是说,Perl只有在成功打开新的句柄连接时,才会关闭默认的系统文件句柄。用say来输出输出内容并换行时,可以使用say函数这是perl 5.10的新特性,可以多试试标量变量中的文件句柄使用裸字和标量变量,根据需要进行选取习题第6章 哈希什么是哈希?名字之前有一个美元符号,之后有一个花括号sv中的关联数组访问哈希元素$hash{$some_key}访问整个哈希用%作为前缀,%hash胖剪头=>左边是键,右边是值;哈希操作函数keys和valuesmy %hash = ('a' => 1, 'b' => 2, 'c' => 3);
my @k = keys %hash;
my @v = values %hash;
my $count = keys %hash; # 返回键值对的个数
if(%hash) {
print "hash 不为空";
}
each函数遍历哈希的每个键值对,可以使用each,每次调用都会返回一个键,一个值while (($key, $value) = each %hash) {
print "$key => $value\n";
}
一个推荐的函数foreach $key (sort keys %hash){
$values = $hash($key);
print "$key => $value\n";
# 或者,可以略去额外的 $value 变量
# print “$key => $hash{$key}\n”;
}
哈希的典型应用exists函数检查哈希中是否存在某个键if (exists $hash{"key"}){
# 存在
}
delete函数直接删除指定的键和值。如果没有这样的键,那就结束,也不会出现警告或错误提示;哈希元素内插foreach $person (sort keys %books) {
if ($books{$person}) {
print "$person has $books{$person} items";
}
}
特殊哈希%ENVPerl会把环境信息放到特殊哈希%ENV里面习题第7章 正则表达式regular expression序列$_ = "yabba dabba doo";
if (/abba/) {
print "It matched\n";
}
如上所示,两个斜线就是匹配操作符号!动手实践不同模式上面的小程序就够了通配符点号.能匹配除换行外的任意单个字符量词指定匹配项的的重复次数。最简单的量词是?:前一个字符出现n次或0次量词*:前一个字符出现n次或0次,常用于匹配不固定长度的空白字符.*:贪婪匹配,可以匹配任意非换行字符任意次模式分组用圆括号( )将模式字符串分组(.)\1:匹配连续出现的两个同样字符择一匹配|:或的关系字符集[a=z] # a到z的全部小写字母
[-a]
# 连字符 或者 a
[^n-z] #不是n到z的字符
简写的反义形式[a]的相反形式可以是[^a]简写匹配\d十进制数字\D非十进制数字\s空白字符\S非空白字符\h水平空白字符(Perl 5.10起支持)\H非水平空白字符(Perl 5.10起支持)\v纵向空白字符(Perl 5.10起支持)\V非纵向空白字符(Perl 5.10起支持)\R一般化的行结尾符号(Perl 5.10起支持)\w单词字符\W非单词字符\n换行符(不是真正的简写)\N非换行符(Perl 5.18 起属于稳定特性)Unicode字符属性锚位\A:匹配字符串的绝对开头\z:匹配字符串的绝对末尾\b:单词锚位,匹配单词边界习题第8章 用正则表达式进行匹配用m//进行匹配模式匹配操作符:m( ),m< >,m{ },m[],其中选择斜线作为定界符时,一般省略前面的m,变成了/ /模式匹配修饰符用/i进行大小写无关的匹配用/s匹配任意字符/.*/s:可以匹配到换行符如果不习惯用/s修饰符,可以使用[\D\d],[\S\s]等,原理就是数字字符以及非数字字符组合就是任意字符。用/x加入辅助空白字符使用这个之后,可以在模式里使用空格或换行使得代码可读性更高!联合使用修饰符单词匹配多想修饰符if (/barney.*fred/is){# 同时使用 /i 和 /s
# 匹配成功
}
用花括号作为定界符,用vim就可以自动定位跳转if (m{
barney
.*
fred
}isx){
# 或同时使用 /i 、 /s 和 /x
# 匹配成功
}
选择字符的解释方式Perl 5.14 开始增加了一些用于告诉Perl如何解释字符意义的修饰符,主要是:对大小写的处理以及对字符集合的阐释。行首和行尾锚位^与\A一样,$与\z一样如果加上/m修饰符,/^/m就会匹配字符串开头和换行符之后的内容(就是每行开头)绑定操作符=~正则表达式默认匹配的目标文本是$_,我们可以使用绑定操作操作符=~指定要匹配的目标文本。捕获变量捕获变量,把匹配到的内容用标量存储起来,方便调用!一般使用圆括号()进行捕获捕获变量的存续期捕获变量的内容一般会保持到下次成功匹配为止,我们可以将其保存下来的;禁用捕获的括号在左括号后加上?:,此时的括号仅用于分组,不再捕获匹配字符串。命名捕获使用如下的捕获标签,可以随意移动位置,并加入更多的捕获括号!use v5.10;
my $names = 'Fred or Barney';
if ($names =~ m/(?<name1>\w+) (?:and|or)(?<name2>\w+)){
say "I saw $+{name1} and $+(name2)";
}
自动捕获变量$`:匹配保存之前的内容$&:匹配保存 的内容$’:匹配保存之后的内容修饰符/p可以对当前的表达式开启类似的自动捕获变量,变成了${^PREMATCH},${^MATCH},${^POSTMARTCH}优先级正则表达式优先级表正则表达式特性示例元括号(分组或捕获)(···),(?:···),(?···)量词a*,a+,a?,a{n,m}锚位和字符序列abc, ^, $, \A, \b,\z,\Z择一a|b|c原子a,[abc],\d,\l,\g{2}模式测试程序程序来源:https://www.learning-perl.com/downloads_page/#!/usr/bin/perl
while (<>) {
# take one input line at a time
chomp;
if (/YOUR_PATTERN_GOES_HERE/) {
print "Matched:
$`<$&>$'|\n";
# the special match vars
} else {
print "No match:
$_|\n";
}
}
习题第9章 用正则表达式处理文本用s///进行替换操作s/st1/st2; #试图将st1替换为st2
用/g进行全局替换s/\s+/ /g;
# 将任意连续的空白转换成单一空格
s/\A\s+//g; # 将开头的空白字符替换成空字符串
s/\s+\z//g; # 将结尾的空白字符替换成空字符串
s/\A\s+|\s+\z//g; #去除开头和结尾的空白字符
不同的定界符和m// 和 qw//一样,可以改变s///的定界符替换操作的修饰符除了/g修饰符外,我们还可以把用在普通模式匹配中的/i、/x和/s修饰符用在替换操作中绑定操作符=~为s///指定不同的替换目标非破坏性替换如果需要同时保留原始字符串和替换后的字符串,该怎么办?复制拷贝一份再替换my $original = 'Fred ate 1 rib';
my $copy = $original;
$copy =~ s/\d+ ribs?/10 ribs/;
等价于(my $copy =
$original )=~ s/\d+ ribs?/10 ribs/;
perl 5.14增加了一个/r字符串,就会保留原来字符串中的值不变,把替换结果作为替换操作的返回值返回use v5.14;
my $copy =
$original =~ s/\d+ ribs?/10 ribs/r;
大小写转换\U将它后面的所有字符转成大写的\L将它后面的所有字符转成小写的默认情况下,它们会影响之后全部的(替换)字符串,可以用\E关闭大小写转换的功能;使用小写\l和\u,它们只会影响紧随其后的第一个字符;同时使用\u与\L来表示"后续字符全部转为小写的,但首字母大写”元字符转义s/\(\(\(Fred/fred/
# 使用\Q简化形式
s/\Q(((Fred/fred/
\
split操作符格式如下:my @fields = split /separator/, $string;
例子如下:注意第二行和第三行my @fields = split /:/,"abc:def:g:h";
# 得到("abc","def","g","h")
my @fields = split /:/,":::a:b:c:::"
# 得到("","","","a","b","c")
my @fields = split /:/,":::a:b:c:::",-1
# 得到("","","","a","b","c","","","")
默认split会以空白字符分割$_中的字符串;my @fields = split; # 基本等效于split /\s+/, $_;
join函数join会把胶水涂进每个片段之间并返回结果字符串my $x = join ":",4,6,8,10,12; # $x 为 “4:6:8:10:12”
my @values = split /:/,$x;
# @values 为(4,6,8,10,12)
my $z = join "-", @values;
# $z为 “4-6-8-10-12”
列表上下文中的m///更强大的正则表达式习题第10章 其他控制结构unless控制结构if条件为真时执行,unless条件为假时执行伴随unless的else语句就是if else改成了unless elseuntil控制结构颠倒while的条件表达式,就用until表达式修饰符简化代码书写即使条件表达式写在后面,它也会先执行倒装句print "$n is a negative number.\n" if $n < 0;
if($n < 0) {
print "$n is a negative number.\n";
}
裸块控制结构{ }主要是为变量限制作用域,和c++一样elsif子句注意不是elseif自增与自减前置后置,自增自减。for控制结构和c语言写法一致for和foreach之间的秘密for和foreach实际上是等价的。括号中有两个分号,就把它当做for,若没有分号,就把他当做foreach循环控制last操作符和c语言的break一样,退出循环next操作符和c语言的continue一样,跳过本次循环redo操作符这不是goto语句封装好的吗?带标签的块这不就是verilog中的程序块别名吗?条件操作符三目运算符? :expression ? if_true_expr : if_false_expr逻辑操作符全套的逻辑操作符短路操作符的返回值短路操作符可以改为三目运算符!使用部分求值操作符的控制结构&&、
、//、?:都是根据左边的值确定要不要执行右边的表达式习题第11章 Perl模块如何使用现有的模块,目的是熟悉使用CPAN完成自己的任务!寻找模块寻找那些没有随Perl发布的模块,可以到CPAN Seach网站(http://search.cpan.org)或MetaCPAN(http://www.metacpan.org)安装模块使用简易模块习题第12章 文件测试文件测试操作符stat和lstat函数loacltime函数位运算操作符习题第13章 目录操作当前工作目录借助标准模块之一Cwd模块,查看当前的工作目录是哪个use v5.10;
use Cwd;
say "The current working directory is ",getcwd();
可以使用标准模块之一File::Spec实现相对路径和绝对路径之间的相互转换\修改工作目录chdir:和shell中的cd一个意思chdir '/etc' or die "cannot chdir to /etc :$!";
可以使用File::HomeDir模块去往特定用户的主目录,他支持大部分操作系统。文件名通配文件名通配:globmy @all_file = glob '*';
my @pm_file = glob '*.pm';
Perl内置的glob并非唯一选择,我们可以用File::Glob模块提供各式兼容和扩展的文件名通配。文件名通配的隐式语法my @all_files = <*>; # 效果和这样的写法完全一致: my
@all_files = glob "*";
Perl 会把尖括号内出现的变量替换成它的值,类似于双引号内字符串的变量内插,如下my $dir = '/etc';
my @dir_file = <$dir/* $dir/.*>;
目录句柄一个例子my $dir_to_process = '/etc';
opendir my $dh, $dir_to_process or die "Cannot open $dir_to_process:$!";
foreach $file (readdir $dh) {
print "one file in $dir_to_process is $file\n";
}
closedir $dh;
使用裸字DIRmy $dir_to_process = '/etc';
opendir DIR, $dir_to_process or die "Cannot open $dir_to_process:$!";
foreach $file (readdir DIR) {
print "one file in $dir_to_process is $file\n";
}
closedir DIR;
文件和目录的操作删除文件unlink:unlink 'slate', 'bedrock','lava';
unlink qw{slate bedrock lava};
unlink glob '*.o';
unlink返回的是成功删除的文件数目,我们可以把他们放到循环依次删除并检查foreach my $file (qw(slate bedrock lava)) {
unlink $file or warn "failed on $file:$!\n";
}
重命名文件rename 'old','new';借用胖剪头=>:如何批量把名称是.old结尾的文件改名为以.new结尾?foreach my $file (glob "*.old") {
my $newfile = $file;
$newfile =~ s/\.old$/.new/;
if (-e $newfile) {
warn "can't rename $file to $newfile: $newfile exists\n"
} elsif(rename $file => $newfile) {
# 改名成功,什么都不需要做
} else {
warn "rename $file to $newfile failed:$!\n";
}
}
循环里的前两行还可以修改为my ($newfile = $file) =~ s/\.old$/.new/;
也可以在Perl 5.14里面加上/r修饰符,use v5.14;
my $newfile = $file =~ s/\.old$/.new/r;
链接与文件与Linux操作系统的文件作对比吧,inode节点等,确定是软链接这个说法吗?(之前有老师强调过,没有软链接这一说,是符号链接和硬链接)创建和删除目录创建失败返回设定值mkdir 'fred', 0755 or warn "Cannot make fred directory: $!";
移除空目录foreach my $dir(qw{fred barney betty}) {
rmdir $dir or warn "cannot rmdir $dir:$!\n";
}
如果要创建临时目录或文件,可以用File::Temp模块修改权限chmod不支持linux中a+x这种格式,除非从CPAN安装了File::chmod.常规格式如下:chmod 0755, 'fred','barney';
修改文件属主chown $user, $group, glob '*.o';
修改时间戳my $now = time;
my $ago = $now - 24 * 60 * 60; # 一天的秒数
utime $now, $ago, glob '*';
# 将最后访问时间改为当前时间,最后修改时间改为一天前
习题第14章 字符串与排序用index查找子字符串my $where = index($stuff, "wor");
用substr操作子字符串my $part = substr($string,$initial_position, $length);
用sprintf格式化字符串my $money = sprintf "%.2f", 2.4997;
高级排序飞碟操作符’<=>’my @descending = sort {$b <=> $a} @some_numbers;
按哈希值排序my %score = ("barney" => 195, "fred" => 205, "dino" => 30);
my @winners = by_score keys %score;
按多个键排序例子1:my %score = (
"barney" => 195, "fred" => 205,
"dino" => 30, "bamm-bamm" => 195,
);
my @winners = by_score_and_name keys %score;
sub by_score_and_name {
$score{$b} <=> $score{$a} #先按照分数降序排列
or
$a cmp $b #分数相同的再按名字的ASCII码序排列
} @winners
例子2:@patron_IDs = sort{
&fines($b) <=> &fines($a) or
$items($b) <=> $items($a) or
$family_name{$a} cmp $family_name{$b} or
$personal_name{$a} cmp $family_name{$b} or
$a <=> $b;
} @patron_IDs;
习题第15章 进程管理习题第16章 高级Perl技巧习题后记本文内容只是记录了小编想学习的部分,其他部分暂时忽略。要是工作中需要的话,再深究!}

我要回帖

更多关于 苹果手机下载软件不见了怎么添加 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信