微软的WPF(Windows Presentation Foundation)是目前Windows平台上最好用的图形界面框架了。如果想在Windows平台上编写图形界面程序,而且没有跨平台且性能需求比较高,而且对C#语言比较熟悉,那么WPF就是最适合你的了。
WPF虽然出来也有大概十来年了,但是它的很多设计思想还是非常先进的,配合C#这门语言的话更加顺手。WPF的界面设计和程序功能完全解耦,也就是说设计界面和编写程序功能可以互不干扰的同时进行。
好了,废话不多说,下面直接开始吧。当然需要说明,这篇文章不是讲如何实现浏览器的,而是利用WPF的一个浏览器控件,让大家了解一下WPF的一些简单功能。由于WPF组件庞大,没办法在一篇文章中详细介绍。所以如果大家通过这篇文章对WPF有了一些兴趣,那么这篇文章的目的就达到了。
先来看看效果图吧。当然功能比较简陋,只有前进、后退、刷新几个功能。当然如果太复杂,就没办法在一篇文章中说完了。
界面设计
布局
不管是什么图形界面框架,首先讨论的都是界面布局了。布局负责组织界面元素如何排列和显示。合适的界面布局可以降低我们程序界面的复杂度。如果需要了解布局的话,可以看看这篇英文文章,或者查找其他中文文章。
这里简单说一下常用的几种布局:
- StackPanel。将部件按照垂直或水平顺序依次排列。
- WrapPanel。和前者差不多,不过如果部件太多,会自动安排到下一行显示。
- DockPanel。可以指定上下左右中五个方位的组件。
- Grid。网格布局,可以按照网格形式排列组件。
现在返回来看看这个浏览器的布局。首先第一行是按钮和地址栏,第二行就是浏览器控件了。所以在这里我使用了DockPanel,第一行我指定为Top;第二行不指定,也就是充满整个剩余空间。
然后来看看第一行的布局,这里我希望前三个按钮按顺序排列,最后的地址栏充满整个剩余空间。所以第一行本身也需要使用DockPanel来实现。
最后来看看相应的XAML代码,虽然说得比较多,但是代码倒是很少。
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Name="ForwardButton" Content="前进" Click="ForwardButton_Click" />
<Button Name="BackButton" Content="后退" Click="BackButton_Click" />
<Button Name="RefreshButton" Content="刷新" Click="RefreshButton_Click" />
<TextBox Name="UrlTextBox" KeyDown="UrlTextBox_KeyDown" />
</DockPanel>
<WebBrowser Name="BrowserControl" />
</DockPanel>
控件
其实关于控件我倒是没什么说的。不管是哪种图形界面,反正控件总是按钮、文本域、标签那些东西。这里我用到的就是按钮和文本框,当然最重要的是WPF提供的浏览器控件WebBrowser,它封装了浏览器的操作以便我们直接使用。
当然WPF还有一个非常重要的特点就是代码可以无缝引用界面控件,这一点将在后面体现。这个特点可是很多图形界面框架不提供的,比方说安卓的代码要引用界面元素的话就得使用getElementById
方法。
样式
最后要说的就是样式了。WPF的样式和HTML的样式在语法上很相似,我们既可以直接在界面元素上指定它的样式,也可以在其他地方统一管理。当然如果要符合软件设计的最佳实践,样式当然需要在一个地方统一指定比较好。
当然,WPF的样式非常丰富,可以对一个控件进行深度定制,让它“重新做人”。所以我就不做介绍了,等到需要的时候在查阅就行了。这里只设置了按钮和文本框的宽度和外边距,外边距的4个值分别代表上、左、右、下外边距。如果不在这里统一设置,那么就要针对每个按钮设置一次外边距,这是件很麻烦的事情。
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="45" />
<Setter Property="Margin" Value="10,10,0,10" />
</Style>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="10,10,10,10" />
</Style>
</Window.Resources>
功能编写
事件处理
说完了界面的部分,下面来说说如何编写程序功能。利用强大的XAML,我们可以非常方便的将界面组件和功能代码对应起来。C#有一个特性叫做事件,WPF也利用了事件来处理程序响应。WPF的控件都包含了大量事件,可以处理鼠标、键盘、触屏等等各种事件,而且仅需要在XAML代码中添加一点代码就可以将事件和处理程序绑定起来。下面代码中的Click
和KeyDown
就是两个事件,用于处理单击鼠标和键盘按键。
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Name="ForwardButton" Content="前进" Click="ForwardButton_Click" />
<Button Name="BackButton" Content="后退" Click="BackButton_Click" />
<Button Name="RefreshButton" Content="刷新" Click="RefreshButton_Click" />
<TextBox Name="UrlTextBox" KeyDown="UrlTextBox_KeyDown" />
</DockPanel>
<WebBrowser Name="BrowserControl" />
</DockPanel>
每个事件的处理函数签名都不相同,比方说单击鼠标的事件签名就是Click(object sender, RoutedEventArgs e)
,而按下键盘的事件签名是KeyDown(object sender, KeyEventArgs e)
。在Visual Studio中我们只需要选择控件,然后点击属性中的相应事件,即可自动生成处理函数,我们只需要编写代码即可。
浏览器控件
说完了事件机制,下面我们来看看如何用它来搞点事情。由于WPF提供了方便的浏览器控件,所以这里的代码非常简单,只需要调用浏览器控件的相应方法即可。由于没有单独的处理按下回车的事件,所以这里用的是按下键盘的事件,然后在处理程序中判断按下的是否是回车键,如果是的话再进行下一步处理,也就是让浏览器导航到对应网址。
private void ForwardButton_Click(object sender, RoutedEventArgs e)
{
BrowserControl.GoForward();
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
BrowserControl.GoBack();
}
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
BrowserControl.Refresh();
}
private void UrlTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
var url = UrlTextBox.Text;
BrowserControl.Navigate(url);
}
}
自动补全URL
上面的代码已经基本可以使用,不过还是有一个小问题。那就是如果输入的不是URL格式(http://www.baidu.com
),而是网址(www.baidu.com
),那么程序就会崩掉。因为浏览器控件只能接受URL形式的字符串,如果不是合法的URL,那么BrowserControl.Navigate(url)
这一句代码就会抛出异常。
那么这个问题该如何解决呢?我在这里直接使用正则表达式做一下测试,如果如果输入的不是有效的URL,那么我就手动在网址前面添加一个 http://
。实现方法很简单,直接看代码就行了。
public partial class MainWindow : Window
{
private readonly Regex _urlPattern = new Regex(@"\w*://\w*(.\w*)+");
public MainWindow()
{
InitializeComponent();
}
//省略了那些事件处理程序
private void UrlTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
var url = UrlTextBox.Text;
if (!_urlPattern.IsMatch(url))
{
url = "http://" + url;
}
BrowserControl.Navigate(url);
}
}
}
如果留心一下前面的XAML就会发现,在下面的代码中直接引用了XAML中浏览器控件的名字BrowserControl
,并可以调用它的属性和方法。这也是浏览·WPF一个非常方便的特性。
更改地址栏URL
下面就剩下最后一个问题了。一般浏览器的地址栏,会随着访问网址的变化而变化。但是我们这个浏览器却没有这个功能,地址栏的地址永远是输入的那个地址。现在我们希望不论是前进、后退,还是从浏览器中点击其他链接,地址栏的地址都会跟着更新。
当然实现这个功能也很简单,查阅一下浏览器控件就可以发现,它有一个属性叫做Source
,恰好就是当前页面的URL,所以利用这个属性就可以完美的实现我们的功能了。这样,只需要一行代码UrlTextBox.Text = BrowserControl.Source.ToString();
就可以搞定了。
当然问题又来了,这行代码应该往哪里加呢?第一种办法是在所有处理程序中添加这行代码, 也就是说,前进、后退的处理程序都需要进行修改。这样并不是一个好办法,万一将来需求发生了变化,有好几处地方都要修改,更容易出错。解决办法还是刚才说的事件。经过一番查找,我发现了WebBrowser
的Navigated
事件,顾名思义,这个事件会在调用了Navigate
方法后触发。这样,只需要把这一行代码绑定到这个事件上就行了,代码非常优雅,酷毙了!
public MainWindow()
{
InitializeComponent();
BrowserControl.Navigated += BrowserControl_Navigated;
}
private void BrowserControl_Navigated(object sender, NavigationEventArgs e)
{
UrlTextBox.Text = BrowserControl.Source.ToString();
}
这样,一个简易的浏览器就实现完毕了。代码我放在了Csdn Code,有兴趣的同学可以看看。