[笔记][连载].NET WPF学习笔记
本帖最后由 Gorgon_Meducer 于 2012-8-10 17:44 编辑Windows Presentation Fundation Study Memo: GENERAL
1.WPF是Windows Presentation Function的缩写。是微软随.Net3.0一起发布的
下一代用户界面模型,用以取代以User32和GDI/GDI+为核心的标准窗体模型。
与传统的窗体模型不同,WPF以DirectX为核心。
2. WPF可以通过XAML来进行描述。但XAML不是必须的,XAML只是一个桥梁用来将
用户代码设计和界面美工设计分开。程序开发人员可以使用Visual Studio
进行初步的界面框架设计,然后可以将具体的界面设计和美化工作通过XAML
的形式交给美工人员通过Expression Blend 或者 Expression Design 进行进
一步美化。
3. XAML直接用来描述和建立WPF的类。为了提高效率,系统通过二进制格式的
XAML也就是BAML进行解析(Parsing)和存储(作为程序的assembly的资源)。
4、WPF界面可以通过三种方式来建立:
a. 纯代码(Code-Only)
这种方法基本和传统的windows窗体没有什么区别。
b. 代码加上XAML
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.IO;
public class Window1 : Window
{
private Button button1;
public Window1()
{
InitializeComponent();
}
private void InitializeComponent()
{
// Configure the form.
this.Width = this.Height = 285;
this.Left = this.Top = 100;
this.Title = "Dynamically Loaded XAML";
// Get the XAML content from an external file.
FileStream s = new FileStream("Window1.xml", FileMode.Open);
DependencyObject rootElement = (DependencyObject)XamlReader.Load(s);
this.Content = rootElement;
// Find the control with the appropriate name.
button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");
// Or you can use equivalent code below
// FrameworkElement frameworkElement = (FrameworkElement)rootElement;
// button1 = (Button)frameworkElement.FindName("button1");
// Wire up the event handler.
button1.Click += button1_Click;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
button1.Content = "Thank you.";
}
}
c. 代码和BAML
这就是Visual Studio设计WPF时系统自动使用的方式。在编译时刻,XAML
会自动生成同名的BAML文件以及一个直接相关的.g.cs文件(如果用C#)。
这个C#文件实际上完成了从资源中载入BAML并对其进行解析的工作。如果
想在应用程序中动态的加载在远程下载到的BAML,也可以参考这些代码,
实现对应的功能。
这一过程可以简单的表述为:
第一阶段:*.XAML -> *.BAML + *.g.cs
第二阶段:*.BAML -> Resource *.g.cs -> Assembly
[注]第二阶段其实就是标准的C#编译了。
加载*.BAML的代码范例代码片断
System.Uri resourceLocater = new System.Uri("window1.baml",
System.UriKind.RelativeOrAbsolute);
System.Windows.Application.LoadComponent(this, resourceLocater);
5、 对一个最小化WPF应用程序的启动来说,下面二者是等效的:using System;
using System.Windows;
public class Startup
{
static void Main()
{
// Create the application.
Application app = new Application();
// Create the main window.
Window1 win = new Window1();
// Launch the application and show the main window.
app.Run(win);
}
}
其中核心代码等效于// Create the application.
Application app = new Application();
// Create, assign, and show the main window.
Window1 win = new Window1();
app.MainWindow = win;
win.Show();
// Keep the application alive.
app.Run();
Visual Studio会根据app.xaml自动生成类似下面的代码:
using System;
using System.Windows;
public partial class App : Application
{
public static void Main()
{
TestApplication.App app = new TestApplication.App();
app.InitializeComponent();
app.Run();
}
public void InitializeComponent()
{
this.StartupUri = new Uri("Window1.xaml", System.UriKind.Relative);
}
}
6、 如果你初学C#开发,直接用WPF开发和用普通的窗体开发基本没有什么差别,甚至更简单一些。
7、 通过Application.Current.MainWindow可以在代码的任意位置获取当前的主窗体;
通过Application.Current.Windows只能获得所有当前处于打开状态的窗体。
本帖最后由 Gorgon_Meducer 于 2012-8-10 15:40 编辑
XAML Syntax and WPF Specified Feature
1. 可以通过下面的方式在XAML中使用用户指定的.Net命名空间里面的类(比如用户自己
建立的类)
xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"
Prefix:只是一个随便的名字,用来代表后面的实际命名空间。
NameSpace: 这就是你想加入的目标.Net命名空间
AssemblyName: 这是一个可选项,如果你要包含的命名空间不再当前工程里面,就需
在这里手工指定。一般就是填写外部动态链接库去掉dll的部分。
具体例子如下:
<Window x:Class="WindowsApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Width="300" Height="300">
<ListBox>
<ListBoxItem>
<sys:DateTime>10/13/2010 4:30 PM</sys:DateTime>
</ListBoxItem>
<ListBoxItem>
<sys:DateTime>10/29/2010 12:30 PM</sys:DateTime>
</ListBoxItem>
<ListBoxItem>
<sys:DateTime>10/30/2010 2:30 PM</sys:DateTime>
</ListBoxItem>
</ListBox>
</Window>
这个例子中,程序加入了System命名空间,相当于"using System;" 并加入了该命名空间下的
类DateTime到ListBox控件中(这里ListBox是WPF控件)
沙发…… 我靠板凳!收藏了,雪中送钱啊!!! 只能地板了。。。 本帖最后由 Gorgon_Meducer 于 2012-8-23 14:39 编辑
Others
1、 如何通过代码将指定的扩展名与指定的应用程序邦定在一起
using Microsoft.Win32;
...
string extension = ".testDoc";
string title = "SingleInstanceApplication";
string extensionDescription = "A Test Document";
FileRegistrationHelper.SetFileAssociation(
extension, title + "." + extensionDescription);该代码只需要运行一次,以后每次双击指定的扩展名,指定的应用程序就会被执行,
并且目标文件会作为应用程序参数传递进来。
2、 如何动态加载和保存WPF对象到BAML
using System.IO;
using System.Windows.Markup;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Build.BuildEngine;
public static class BamlWriter
{
public static void Save(object obj, Stream stream)
{
string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(path);
try
{
string xamlFile = Path.Combine(path, "input.xaml");
string projFile = Path.Combine(path, "project.proj");
using (FileStream fs = File.Create(xamlFile))
{
XamlWriter.Save(obj, fs);
}
Engine engine = new Engine();
engine.BinPath = RuntimeEnvironment.GetRuntimeDirectory();
Project project = engine.CreateNewProject();
BuildPropertyGroup pgroup = project.AddNewPropertyGroup(false);
pgroup.AddNewProperty("AssemblyName", "temp");
pgroup.AddNewProperty("OutputType", "Library");
pgroup.AddNewProperty("IntermediateOutputPath", ".");
pgroup.AddNewProperty("MarkupCompilePass1DependsOn", "ResolveReferences");
BuildItemGroup igroup = project.AddNewItemGroup();
igroup.AddNewItem("Page", "input.xaml");
igroup.AddNewItem("Reference", "WindowsBase");
igroup.AddNewItem("Reference", "PresentationCore");
igroup.AddNewItem("Reference", "PresentationFramework");
project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.CSharp.targets", null);
project.AddNewImport(@"$(MSBuildBinPath)\Microsoft.WinFX.targets", null);
project.FullFileName = projFile;
if (engine.BuildProject(project, "MarkupCompilePass1"))
{
byte[] buffer = new byte;
using (FileStream fs = File.OpenRead(Path.Combine(path, "input.baml")))
{
int read = 0;
while (0 < (read = fs.Read(buffer, 0, buffer.Length)))
{
stream.Write(buffer, 0, read);
}
}
}
else
{
// attach a logger to the Engine if you need better errors
throw new System.Exception("Baml compilation failed.");
}
}
finally
{
Directory.Delete(path, true);
}
}
}
public static class BamlReader
{
public static object Load(Stream stream)
{
ParserContext pc = new ParserContext();
return typeof(XamlReader)
.GetMethod("LoadBaml", BindingFlags.NonPublic | indingFlags.Static)
.Invoke(null, new object[] { stream, pc, null, false });
}
}
3、 可以通过下面的方法获得系统当前所有有效的字体:foreach (FontFamily fontFamily in Fonts.SystemFontFamilies)
{
lstFonts.Items.Add(fontFamily.Source);
}4、 在任意时刻都可以通过静态方法Mouse.OverrideCursor来强行指定
当前的鼠标指针状态。例如:Mouse.OverrideCursor = Cursors.Wait;5、 可以通过下面的方法动态的从Assembly里加载鼠标指针资源:StreamResourceInfo sri = Application.GetResourceStream(
new Uri("stopwatch.ani", UriKind.Relative));
Cursor customCursor = new Cursor(sri.Stream);
this.Cursor = customCursor;4、 每一个APP都可以通过IsolateStorageFile类来构建一个独享的虚拟存储空间,
大小是512KB,这是一种比临时文件更安全的方式——当然在空间大小满足,
要求的情况下。使用例程如下:// Write to isolated storage.
try
{
IsolatedStorageFile store =
IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream fs = new IsolatedStorageFileStream("highscores.txt", FileMode.Create, store))
{
WriteHighScores(fs);
}
}
catch { ... }5、 可以通过System.IO.Compression下的DeflateStream 或者 GZipStream 来实现文件(流)的压缩。
6、 从指定的Assembly中获取资源文件的例子:img.Source = new BitmapImage(
new Uri("ImageLibrary;component/images/winter.jpg", UriKind.Relative));指定Assembly的同时指定其版本:img.Source = new BitmapImage(
new Uri("ImageLibrary;v1.25;component/images/winter.jpg",
UriKind.Relative));指定版本的同时指定public key:img.Source = new BitmapImage(
new Uri("ImageLibrary;v1.25;dc642a7f5bd64912;component/images/winter.jpg",
UriKind.Relative));7、 通过列表类控件删除DataRow的时候,下面的方法是错误的:products.Rows.Remove((DataRow)lstProducts.SelectedItem);在这里,products是一个DataTable对象。错误的原因有二:首先,SelectedItem实际上不是DataRow而是DataRowView
封装后的结构;其次,通常我们删除DataRow并不是想从DataTable中删除该行,而是想在DataTable中将其标记为删除,
这样在我们与后台数据库进行同步的更新的时候才会将对应的内容删除。基于以上原因,应该使用以下的代码:((DataRowView)lstProducts.SelectedItem).Row.Delete(); 顶,这个东西用过一下下,觉得做出来的效果确实很帅气~ 本帖最后由 Gorgon_Meducer 于 2012-8-23 14:15 编辑
Architecture and Infrastructure
[注]以下内容仅就需要注意的要点作了摘要性的记录,详细内容请参考WPF相关的章节。
1、 Dependency Property的Validation Callback必须满足如下的要求
a. 静态属性,以Object为输入参数,返回Boolean值表示输入的属性是否有效;
b. 不能操作传入的Object;
c. 只能检查当前属性,不应该把目标对象的其他属性考虑在内。比如:在检查
最大值属性Maximum的时候,不能检查Minimum值,使得当Maximum小于Minimum
的时候给Maximum赋值——这种操作是不允许的,同时违反了以上两条限制。
解决上述情形的方法应该是使用Value Coercion。
2、 虽然Dependency Property的实例在描述的时候加入了ReadOnly标志,但并不表示
该属性是不能修改的——ReadOnly只是限制了传统的修改方式。正确的修改和读取
方式应该通过DependencyProperty类的方法SetValue()和GetValue()来进行,例如:
public Thickness Margin
{
set { SetValue(MarginProperty, value); }
get { return (Thickness)GetValue(MarginProperty); }
}
上述方式成为Dependency Property的属性封装,也就是把Dependency Property
封装成普通的属性。因为WPF系统自己会直接调用SetValue()和GetValue()而不是
这个封装后的普通属性,因此在属性封装的set和get方法中进行额外的类型检测,
事件处理是没有意义的,或者说仅针对Code访问的模式有效。
3、 Dependency Property使用Callback进行进行Validation和Event Raising。
Validation通过DependencyProperty.ValidateValueCallback来进行;
Event Raising可以通过DependencyProperty.PropertyChangedCallback进行
4、 在我们给一个Dependency Property设置值之前,WPF实际上已经通过各种途径给它
设置了默认值。在某些场合下我们希望在赋值后重新使用WPF的默认值,可以通过
DpendencyObject的方法ClearValue来实现。例如:
myElement.ClearValue(FrameworkElement.MarginProperty);
5、 ValidateValueCallback在DependencyProperty.Register()调用时作为参数传入;
PropertyChangedCallback和CoerceValueCallback在建立
FrameworkPropertyMetadata对象时作为构造函数的参数传入。WPF中他们的调用顺
序如下:
1) CoerceValueCallback首先被调用,它可以修改传入的值,或者通过返回特定的
值DependencyProperty.UnsetValue来拒绝传入的值(告知修改失败)。例如:
private static object CoerceMaximum(DependencyObject d, object value)
{
RangeBase base1 = (RangeBase)d;
if (((double) value) < base1.Minimum)
{
return base1.Minimum;
}
return value;
}
2) ValidateValueCallback会随后被调用,返回true或false表示传入的值是否有效。
该方法不能访问所要修改属性的目标对象,而CoerceValueCallback可以。
3) PropertyChangedCallback如果上述两步都通过了,则该Callback会被调用。事
件就可以通过该方法来触发。
6、 在有些情形下需要手工出发DependencyProperty的Coerce方法。例如:
private static void OnMinimumChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
RangeBase base1 = (RangeBase)d;
...
base1.CoerceValue(RangeBase.MaximumProperty);
base1.CoerceValue(RangeBase.ValueProperty);
}
7、 考虑下面的代码:
ScrollBar bar = new ScrollBar();
bar.Value = 100;
bar.Minimum = 1;
bar.Maximum = 200;
执行完最后一句以后bar.Value应该是100而不是1。这是因为WPF在内部会保存
DependencyProperty的原始值,并通过Coerce系统来调整有效值。bar初始化的
时候最小值是0,最大值是1,当bar.Maximum被设置为200时,原始的100就成为
有效值了。神奇吧!强大吧!见鬼吧!
8、 在WPF中Attached Property和普通的Dependency Property没有本质区别,注册
Dependency Property的时候使用Register()方法还是RegisterAttached()方法
只是限制了XAML是否将该Dependency Property看作Attached Property。在代码
中,二者没有任何区别。
9、 在使用DataBinding技术的时候,如果源对象是一个普通的类(不是Dependency-
Object的子类),那么可以通过实现INotifyPropertyChanged接口来实现数据的
Update功能。在使用的过程中,如果类的任何属性被改变了,只要raise该类,并
将对应的属性名用字符串的形式给出就可以一劳永逸的解决Update问题。如果同
时有多个属性改变了,只需要传递空字符串就可以了。
10、大部分系统自带的Collection(包括List)都没有实现接口INotifyCollectionChanged
因此,当Collection绑定到列表类的控件时无法对插入和删除的操作作出对应的更
新。在WPF中只有ObservableCollection类实现了这一接口。
看过你的《深入浅出AVR单片机》,第一次离“傻孩子”这么近,好激动。 wugang_1213 发表于 2012-8-14 00:15 static/image/common/back.gif
看过你的《深入浅出AVR单片机》,第一次离“傻孩子”这么近,好激动。
荣幸荣幸~
莫激动,都是研究技术的,淡定~淡定~ 本帖最后由 Gorgon_Meducer 于 2012-8-14 12:45 编辑
Events
1、 在KeyDown、PreviewKeyDown、KeyUp和PreviewKeyUp事件中,获取的e.Key值实际上并不能准确地表示所按下的
键表示哪个字母,比如小数字键盘的9和主键盘的九实际上由两个不同的e.Key值表示的,可以通过下面的代码获得
实际的内容:KeyConverter converter = new KeyConverter();
string key = converter.ConvertToString(e.Key);2、 可以用PreviewTextInput事件和PreviewKeyDown组合的方式来限制TextBox这样内部取消了TextInput事件的控件输入
的内容,比如下面的代码限制了在TextBox中只能输入数字:private void pnl_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
short val;
if (!Int16.TryParse(e.Text, out val))
{
// Disallow non-numeric keypresses.
e.Handled = true;
}
}
private void pnl_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
// Disallow the space key, which doesn't raise a PreviewTextInput event.
e.Handled = true;
}
}3、 在任意时刻都可以通过Keyboard类来获取键盘状态,比如:if (Keyboard.IsKeyDown(Key.LeftShift))
{
lblInfo.Text = "The left Shift is held down.";
}4、 在任意时刻都可以通过Mouse类来获取鼠标状态。Mouse类提供了一个有用的方法Mouse.Captuer()。该方法可以强制
让指定的对象接收到鼠标的事件,同一应用的其它对象都将无法接收对应的鼠标事件。如果用户在使用Capture的状态
下如果单击了MessageBox或者其它应用程序的窗口,都将丢失Capture状态,用户可以通过一个专门的事件来处理这种
情况:LostMouseCapture。
本帖最后由 Gorgon_Meducer 于 2012-8-21 14:50 编辑
Resources, Localization & Template
1、 每一个Template都应该放在独立的ResourceDictionary里面。方法:选中Solution Explorer中当前的Project->New Item
建立一个新的ResourceDictionary,例如<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
...
</ResourceDictionary>在Application的资源中,将分散的Template通过MergedDictionaries方法集中起来,例如
<Application x:Class="SimpleApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\Button.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>2、 使用Control Template来重构(Refactoring)目标控件的骨架;
用TemplateBinding来暴露可配置的信息给Style;
用Style来将Control Template和各种不同的配色风格以及Trigger绑定在一起;
所有这些都要以个体或者集合的形式存放在专门的ResourceDictionary里面。
3、 可以通过指向特定Element的匿名Style来实现Template与控件的自动绑定(自动换肤)。例如:<Style TargetType="{x:Type Button}">
<Setter Property="Control.Template" Value="{StaticResource ButtonTemplate}"
</Style> Qt的研究过没,界面使用XML写的 wazhiyi 发表于 2013-12-18 13:21
Qt的研究过没,界面使用XML写的
有接触过一点点,浅尝辄止。XML很好用,我也比较喜欢用的。 貌似这种代码和xml结合的方法很流行……,android也是这样的,可以直接在代码里穿件控件,也可以在xml中定义界面,只不过xml写出来的界面是静止的,需要代码来实现逻辑。同时也提供类似VB那种拖放空间的方式,直接生成xml文件,很便利 {:biggrin:}太好了,好好学学习 请问一下楼主,wpf开发和mfc开发区别是啥?我是最近才了解这个玩意儿,初步感觉他两就是一样的,只是wpf比较炫 yanpenghao 发表于 2014-7-21 14:41
请问一下楼主,wpf开发和mfc开发区别是啥?我是最近才了解这个玩意儿,初步感觉他两就是一样的,只是wpf比 ...
一个是前一代的,一个是下一代的。MFC已经是老古董了。 Gorgon_Meducer 发表于 2014-7-21 16:14
一个是前一代的,一个是下一代的。MFC已经是老古董了。
那开发起来效率怎么样啊?wpf是不是C#的?可以用C++么? 目前WPF的潜力如何? jobmen 发表于 2017-11-17 22:38
目前WPF的潜力如何?
大势所趋,谈潜力太迟了 wpf是大势所趋吗? hyf88 发表于 2018-12-14 21:45
wpf是大势所趋吗?
我不知道,这是大约6年前的帖子…… hyf88 发表于 2018-12-14 21:45
wpf是大势所趋吗?
已经6年过去了 微软的方向感觉不明确,刚开始那几年把wpf捧的很高 因为我也是学了一段时间的wpf,感觉不知道怎么评价,微软的封装太深了,
都担心以后不会写程序了,都是封装好的,
页:
[1]