WPF+学习笔记.docx
《WPF+学习笔记.docx》由会员分享,可在线阅读,更多相关《WPF+学习笔记.docx(71页珍藏版)》请在冰豆网上搜索。
![WPF+学习笔记.docx](https://file1.bdocx.com/fileroot1/2022-11/25/8ffecbde-7993-4fdf-926d-88b13754364a/8ffecbde-7993-4fdf-926d-88b13754364a1.gif)
WPF+学习笔记
WPF学习笔记
目录
WPF学习笔记1
Application1
Dispatcher3
Navigation5
XAML9
DependencyProperty15
RoutedEvent20
Resource24
Binding31
Silverlight-Hello,World!
68
Application
和WinForm类似,WPF同样需要一个Application来统领一些全局的行为和操作,并且每个Domain中只能有一个Application实例存在。
和WinForm不同的是WPFApplication默认由两部分组成:
App.xaml和App.xaml.cs,这有点类似于DelphiForm,将定义和行为代码相分离。
当然,WebForm也采用了类似的方式。
XAML从严格意义上说并不是一个纯粹的XML格式文件,它更像是一种DSL,它的所有定义都直接映射成某些代码,只不过具体的翻译工作由编译器完成而已。
下面是一个简单的App定义。
publicpartialclassApp:
Application
{
}
当你在自动生成的Project代码中看到paritial时,应该下意识去找找"Thiscodewasgeneratedbyatool."……不过这次自动生成的代码存放位置更加古怪——obj\Debug\App.g.cs。
publicpartialclassApp:
System.Windows.Application
{
[DebuggerNonUserCode]
publicvoidInitializeComponent()
{
this.StartupUri=newSystem.Uri("Window1.xaml",System.UriKind.Relative);
}
[STAThread]
[DebuggerNonUserCode]
publicstaticvoidMain()
{
Appapp=newApp();
app.InitializeComponent();
app.Run();
}
}
App.StartupUri用于设置MainWindow,App.Run()启动消息循环。
当然,还有那个STAThread,这意味着WPF依旧使用一个UIThread来处理UIMessage。
我们完全可以舍弃自动生成的代码,自己手工写一个App。
publicclassApp:
Application
{
[STAThread]
privatestaticvoidMain()
{
varapp=newApp();
varwindow=newWindow{Title="WPF"};
app.Run(window);
}
}
Application提供了一些实用的属性和方法。
Current:
获取Domain中默认的Application实例。
MainWindow:
获取主窗口实例。
Windows:
获取所有被实例化的Window实例。
ShutdownMode:
指定Application.Shutdown方式,包括主窗体关闭,最后一个窗口关闭,以及手工调用Shutdown()。
Properties:
一个线程安全的全局字典,可用来存储一个公共信息。
Shutdown:
该方法终止ApplicationProcess,可向操作系统返回一个退出码。
我们依然可以使用Mutex来阻止运行多个实例。
privatevoidApplication_Startup(objectsender,StartupEventArgse)
{
varcreatedNew=false;
varname=Assembly.GetEntryAssembly().FullName;
newMutex(true,name,outcreatedNew);
if(!
createdNew)
{
MessageBox.Show("Thereisalreadyaninstancerunning,Exit!
");
Application.Current.Shutdown();
}
}
当然也可以用Windows属性判断窗体是否已经存在。
privatevoidbutton1_Click(objectsender,RoutedEventArgse)
{
varwindow2=Application.Current.Windows.OfType().FirstOrDefault(w=>wisWindow2);
if(window2==null)window2=newWindow2();
window2.Show();
window2.Activate();
}
---------------------无聊的分割线--------------------
迟到的笔记总算开始了,算是为Silverlight做准备吧。
Dispatcher
WPF使用一个专用的UI线程来完成界面的操作和更新,这个线程会关联一个唯一的Dispatcher对象,用于调度按优先顺序排列的工作项队列。
Application.Run()实际上就是对Dispatcher.Run()的间接调用。
Dispatcher通过循环来处理工作项队列,这个循环通常被成为"帧(DispatcherFrame)"。
Dispatcher.Run()创建并启动这个帧,这也是Application.Run()启动消息循环的最终途径。
publicsealedclassDispatcher
{
[SecurityCritical,UIPermission(SecurityAction.LinkDemand,Unrestricted=true)]
publicstaticvoidRun()
{
PushFrame(newDispatcherFrame());
}
}
DispatcherFrame可以嵌套,并通过检查Continue属性来决定循环是否继续。
我们可以通过调用Dispatcher.ExitAllFrames()来终止所有的帧循环,当然这种编程方式并不可取,可能会造成一些意外出现。
与Dispatcher调度对象想对应的就是DispatcherObject,在WPF中绝大部分控件都继承自DispatcherObject,甚至包括Application。
这些继承自DispatcherObject的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了Dispatcher的线程(通常指默认UI线程)才能直接对其进行更新操作。
当我们尝试从一个非UI线程更新一个标签,会看到一个如下的异常。
privatevoidbutton1_Click(objectsender,RoutedEventArgse)
{
newThread(()=>this.label1.Content=DateTime.Now.ToString()).Start();
}
按照DispatcherObject的限制原则,我们改用Window.Dispatcher.Invoke()即可顺利完成这个更新操作。
privatevoidbutton1_Click(objectsender,RoutedEventArgse)
{
newThread(()=>
{
this.Dispatcher.Invoke(DispatcherPriority.Normal,
newAction(()=>this.label1.Content=DateTime.Now.ToString()));
}).Start();
}
如果在其他项目(比如类库)中,我们可以用Application.Current.Dispatcher.Invoke(...)完成同样的操作,它们都指向UIThreadDispatcher这个唯一对象。
Dispatcher还提供了BeginInvoke这个异步版本。
privatevoidbutton1_Click(objectsender,RoutedEventArgse)
{
newThread(()=>
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
newAction(()=>
{
Thread.Sleep(3000);
this.label1.Content=DateTime.Now.ToString();
}));
MessageBox.Show("Hi!
");
}).Start();
}
凡事都有例外,WPF还提供了一种继承自Freezable的类型,尽管Freezable也间接继承自DispatcherObject,但当这类对象从修改状态变成冻结状态时,它即变成自由线程对象,不在具有线程关联。
(有关Freezable详情可参考MSDN)
Navigation
互联网的兴起,造就和培养了一种新的用户交互界面——Page&Navigation。
无论是前进、后退还是页面,都完全是一个全新的门类,不同于以往的SDI/MDI。
WPF或者是它的简化版Silverlight都不可避免地遵从了这种改良的B/S模式,使用URI来串接UI流程。
NavigationService、Page、Hyperlink、Journal(日志/历史记录)是WPF整个导航体系的核心。
NavigationService提供了类似IEHost的控制环境,Journal可以记录和恢复相关Page的状态,我们通常会选用的宿主方式包括:
Browser(XBAP)和NavigationWindow。
1.NavigationWindow
NavigationWindow继承自Window,不知什么原因,我并没有在VS2008"NewItem..."中找到相关的条目,只好自己动手将一个Window改成NavigationWindow。
Window1.xaml
Class="Window1"
xmlns="
xmlns:
x="
Title="Window1"Height="300"Width="300"WindowStartupLocation="CenterScreen"
Source="Page1.xaml">
Source属性指定了该窗口的默认页面,当然,我们还要修改一下Window1.xaml.cs里的基类。
publicpartialclassWindow1:
NavigationWindow
{
publicWindow1()
{
InitializeComponent();
}
}
创建一个Page1.xaml,我们就可以像普通Window那样添加相关的控件和操作。
2.Hyperlink
超链接应该是我们最熟悉的一种导航方式。
Page1.xaml
Class="Learn.WPF.Page1"
xmlns="
xmlns:
x="
Title="Page1">
Page2
NavigateUri相当于"Htmla.href",当然我们也可以使用Hyperlink.Click事件,然后使用NavigationService来完成导航操作。
Page1.xaml
Page2
Page1.xaml.cs
privatevoidHyperlink_Click(objectsender,RoutedEventArgse)
{
this.NavigationService.Navigate(newUri("Page2.xaml",UriKind.Relative));
}
Hyperlink还支持"test.htm#name"这样的导航定位方式,滚动页面直到某个特定名称的控件被显示。
Hyperlink的另外一个实用属性是Command,我们可以使用NavigationCommands中创建的一系列静态成员来执行一些常用操作。
Refresh
BrowseBack
BrowseForward
3.NavigationService
很多时候我们都需要使用NavigationService代替Hyperlink.NavigateUri,比如非默认构造的Page,动态确定目标页面等等。
我们可以使用Page.NavigationService或者NavigationService.GetNavigationService()获得NavigationService的实例引用(别忘了添加usingSystem.Windows.Navigation)。
publicpartialclassPage1:
Page
{
privatevoidHyperlink_Click(objectsender,RoutedEventArgse)
{
varpage2=newPage2();
page2.label1.Content="Beijing2008!
";
this.NavigationService.Navigate(page2);
}
}
除了Navigate(),还可以使用NavigationService的两个属性完成导航切换操作。
//this.NavigationService.Content=page2;
this.NavigationService.Source=newUri("Page2.xaml",UriKind.Relative);
NavigationService提供了大量的方法和时间来管理相关导航操作。
日志:
AddBackEntry、RemoveBackEntry。
载入:
Navigate、Refresh、StopLoading。
切换:
GoBack、GoForward。
事件:
Navigating(新导航请求时触发,可取消导航)……
我们也可以使用Application的相关事件来处理导航过程。
4.Journal
Journal相当于WebBrowser.History,它包含两个数据栈用来记录前进和后退页面的显示状态,每个相关Page都会对应一个JournalEntry。
日志状态自动恢复仅对单击导航条上前进后退按钮有效。
5.Page
有关Page本身的使用并不是本文的内容,我们此处关心的是它在导航过程中的生命周期。
在WPF中,Page注定是个短命鬼,无论我们使用导航还是后退按钮都会重新创建Page对象实例,然后可能是日志对其恢复显示状态。
也就是说日志只是记录了Page相关控件的状态数据,而不是Page对象引用(默认情况下)。
有两种方式来维持一个Page引用。
第一种就是我们自己维持一个Page引用,比如使用某个类似Application.Properties这样的容器。
privatevoidHyperlink_Click(objectsender,RoutedEventArgse)
{
varpage2=Application.Current.Properties["page2"]asPage2;
if(page2==null)
{
page2=newPage2();
page2.label1.Content=DateTime.Now.ToString();
Application.Current.Properties["page2"]=page2;
}
this.NavigationService.Navigate(page2);
//this.NavigationService.Content=page2;
}
另外一种就是设置Page.KeepAlive属性,这样一来日志会记录该Page的引用,当我们使用前进后退按钮时,将不会再次创建该Page的对象实例。
Class="Learn.WPF.Page2"
xmlns="
xmlns:
x="
Title="Page2"Loaded="Page_Loaded"
KeepAlive="True">
有一点需要注意:
该方法仅对前进后退等日志操作有效。
如果我们使用HyperLink.NavigateUri或NavigationService.Navigate()导航时依旧会生成新的页面实例,并可能代替日志中最后一个同类型的对象引用记录。
另外,当多个页面存在循环链接时,会导致多个页面实例被日志记录,造成一定的内存浪费。
6.Frame
Frame的作用和HTML中的IFrame类似,我们可以用它在一个普通的Window或Page中嵌套显示其他的Page。
Class="Learn.WPF.Window1"
xmlns="
xmlns:
x="
Title="Window1"Height="300"Width="300">
默认情况下,Frame会尝试使用上层页面(Page)或窗体(NavigationWindow)的日志,当然我们也可以使用JournalOwnership属性强行让Frame使用自己的日志导航。
Frame的另外一个作用就是可以导航到HTML页面,我们可以把它当作一个嵌入式IEWebBrowser来使用。
7.PageFunction
WPF提供了一个称之为PageFunction的Page继承类来实现类似HTMLshowModal的功能。
我们可以用它来收集某些数据并返回给调用页,当然这个封装其实非常简单,我们完全可以自己实现,无非是提供一个类似OnReturn的方法实现而已。
泛型参数T表示返回数据类型。
Page1.xaml.cs
publicpartialclassPage1:
Page
{
privatevoidHyperlink_Click(objectsender,RoutedEventArgse)
{
varmodal=newPageFunction1();
modal.Return+=(s,ex)=>this.label1.Content=ex.Result.ToString();
this.NavigationService.Navigate(modal);
}
}
PageFunction1.xaml.cs
publicpartialclassPageFunction1:
PageFunction
{
privatevoidbutton1_Click(objectsender,RoutedEventArgse)
{
OnReturn(newReturnEventArgs(DateTime.Now.Millisecond));
}
}
使用步骤:
(1)创建PageFunction对象实例,当然我们可以使用含参构造传递额外的数据;
(2)调用PageFunction.OnReturn()方法用来返回一个特定的结果包装对象——ReturnEventArgs;
(3)调用者通过订阅Page