Property与Attribute - 《Windows Presentation Foundation 程序设计指南》 - 免费试读 - book.csdn.net

第20章 Property与Attribute
你在上一章遇到的XamlReader.Load方法可能会是一个方便的编程工具。假设你有一个TextBox,而且你为TextChanged事件设置了处理函数。当你在此TextBox内键入XAML,此TextChanged事件处理函数可以试着将XAML传递给XamlReader.Load方法,并显示出结果对象。你需要将XamlReader.Load调用放在try区块中,因为在输入XAML的过程中,大多数的时候,XAML都是无效的(invalid),但是这样的编程工具可以立即响应你的XAML实验。这会是一个很棒的XAML学习辅助工具,也是很有趣的工具。
这就是XAML Cruncher程序的前提。它绝对不是第一个这一类的程序,也不会是最后一个。XAML Cruncher是建立在Notepad Clone之上的。你将会看到, XAML Cruncher用一个Grid取代Notepad Clone客户区的TextBox。此Grid让TextBox占用一个格子,一个Frame控件占用另一个,两者之间是一个GridSplitter。当你键入TextBox的XAML被XamlReader.Load成功地转成一个对象,此对象会成为Frame的Content。
此XamlCruncher工程包含NotepadClone工程中的每一个文件,只有一个不包含在内: NotepadCloneAssemblyInfo.cs。此文件用下面的文件取代:
XamlCruncherAssemblyInfo.cs
//---------------------------------------------------------
// XamlCruncherAssemblyInfo.cs (c) 2006 by Charles Petzold
//---------------------------------------------------------
using System.Reflection;
[assembly: AssemblyTitle("XAML Cruncher")]
[assembly: AssemblyProduct("XamlCruncher")]
[assembly: AssemblyDescription("Programming Tool Using XamlReader.Load")]
[assembly: AssemblyCompany("www.charlespetzold.com")]
[assembly: AssemblyCopyright("/x00A9 2006 by Charles Petzold")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.0")]
你应该记得,NotepadCloneSettings类包含数个被保存为用户偏好的项目。XamlCruncherSettings类继承自NotepadCloneSettings,只加入3个项目。第一个是Orientation,负责TextBox和Frame的方向。XAML Cruncher有一个菜单项,让你可以将TextBox和Frame的其中一个放在另一个上面,或者让两者同时并列出现。另外,XAML将正常的TextBox对Tab按键的处理改掉,改成插入空格(space)。第二个用户偏好,是Tab按键所插入空格的数字。
第三个用户偏好是一个简单的XAML字符串,当你第一次执行此程序,或者当你选取File菜单的New菜单项,该字符串就会出现在TextBox中。一个菜单项让你可以将目前TextBox的内容,设定为此启动文件项目。
XamlCruncherSettings.cs
//-----------------------------------------------------
// XamlCruncherSettings.cs (c) 2006 by Charles Petzold
//-----------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Petzold.XamlCruncher
{
public class XamlCruncherSettings : Petzold.NotepadClone.NotepadCloneSettings
{
// Default settings of user preferences.
public Dock Orientation = Dock.Left;
public int TabSpaces = 4;
public string StartupDocument =
" /r/n";
// Constructor to initialize default settings in NotepadCloneSettings.
public XamlCruncherSettings()
{
FontFamily = "Lucida Console";
FontSize = 10 / 0.75;
}
}
}
除此之外,XamlCruncherSettings构造函数将默认的字体改成10 point的Lucida Console。当然,当你执行XAML Cruncher时,你可以改变成你想要的字体。
下面是XamlCruncher类,继承自NotepadClone类。此类负责建立一个Grid作为Window新的内容,也建立Xaml顶层菜单项和其子菜单中的6个项目。
XamlCruncher.cs
//---------------------------------------------
// XamlCruncher.cs (c) 2006 by Charles Petzold
//---------------------------------------------
using System;
using System.IO; // for StringReader
using System.Text; // for StringBuilder
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives; // for StatusBarItem
using System.Windows.Input;
using System.Windows.Markup; // for XamlReader.Load
using System.Windows.Media;
using System.Windows.Threading; // for DispatcherUnhandledExceptionEventArgs
using System.Xml; // for XmlTextReader
namespace Petzold.XamlCruncher
{
class XamlCruncher : Petzold.NotepadClone.NotepadClone
{
Frame frameParent; // To display object created by XAML.
Window win; // Window created from XAML.
StatusBarItem statusParse; // Displays parsing error or OK.
int tabspaces = 4; // When Tab key pressed.
// Loaded settings.
XamlCruncherSettings settingsXaml;
// Menu maintenance.
XamlOrientationMenuItem itemOrientation;
bool isSuspendParsing = false;
[STAThread]
public new static void Main()
{
Application app = new Application();
app.ShutdownMode = ShutdownMode.OnMainWindowClose;
app.Run(new XamlCruncher());
}
// Public property for menu item to suspend parsing.
public bool IsSuspendParsing
{
set { isSuspendParsing = value; }
get { return isSuspendParsing; }
}
// Constructor.
public XamlCruncher()
{
// New filter for File Open and Save dialog boxes.
strFilter = "XAML Files(*.xaml)|*.xaml|All Files(*.*)|*.*";
// Find the DockPanel and remove the TextBox from it.
DockPanel dock = txtbox.Parent as DockPanel;
dock.Children.Remove(txtbox);
// Create a Grid with three rows and columns, all 0 pixels.
Grid grid = new Grid();
dock.Children.Add(grid);
for (int i = 0; i < 3; i++)
{
RowDefinition rowdef = new RowDefinition();
rowdef.Height = new GridLength(0);
grid.RowDefinitions.Add(rowdef);
ColumnDefinition coldef = new ColumnDefinition();
coldef.Width = new GridLength(0);
grid.ColumnDefinitions.Add(coldef);
}
// Initialize the first row and column to 100*.
grid.RowDefinitions[0].Height =
new GridLength(100, GridUnitType.Star);
grid.ColumnDefinitions[0].Width =
new GridLength(100, GridUnitType.Star);
// Add two GridSplitter controls to the Grid.
GridSplitter split = new GridSplitter();
split.HorizontalAlignment = HorizontalAlignment.Stretch;
split.VerticalAlignment = VerticalAlignment.Center;
split.Height = 6;
grid.Children.Add(split);
Grid.SetRow(split, 1);
Grid.SetColumn(split, 0);
Grid.SetColumnSpan(split, 3);
split = new GridSplitter();
split.HorizontalAlignment = HorizontalAlignment.Center;
split.VerticalAlignment = VerticalAlignment.Stretch;
split.Height = 6;
grid.Children.Add(split);
Grid.SetRow(split, 0);
Grid.SetColumn(split, 1);
Grid.SetRowSpan(split, 3);
// Create a Frame for displaying XAML object.
frameParent = new Frame();
frameParent.NavigationUIVisibility = NavigationUIVisibility.Hidden;
grid.Children.Add(frameParent);
// Put the TextBox in the Grid.
txtbox.TextChanged += TextBoxOnTextChanged;
grid.Children.Add(txtbox);
// Case the loaded settings to XamlCruncherSettings.
settingsXaml = (XamlCruncherSettings)settings;
// Insert "Xaml" item on top-level menu.
MenuItem itemXaml = new MenuItem();
itemXaml.Header = "_Xaml";
menu.Items.Insert(menu.Items.Count - 1, itemXaml);
// Create XamlOrientationMenuItem & add to menu.
itemOrientation =
new XamlOrientationMenuItem(grid, txtbox, frameParent);
itemOrientation.Orientation = settingsXaml.Orientation;
itemXaml.Items.Add(itemOrientation);
// Menu item to set tab spaces.
MenuItem itemTabs = new MenuItem();
itemTabs.Header = "_Tab Spaces...";
itemTabs.Click += TabSpacesOnClick;
itemXaml.Items.Add(itemTabs);
// Menu item to suppress parsing.
MenuItem itemNoParse = new MenuItem();
itemNoParse.Header = "_Suspend Parsing";
itemNoParse.IsCheckable = true;
itemNoParse.SetBinding(MenuItem.IsCheckedProperty,
"IsSuspendParsing");
itemNoParse.DataContext = this;
itemXaml.Items.Add(itemNoParse);
// Command to reparse.
InputGestureCollection collGest = new InputGestureCollection();
collGest.Add(new KeyGesture(Key.F6));
RoutedUICommand commReparse =
new RoutedUICommand("_Reparse", "Reparse",
GetType(), collGest);
// Menu item to reparse.
MenuItem itemReparse = new MenuItem();
itemReparse.Command = commReparse;
itemXaml.Items.Add(itemReparse);
// Command binding to reparse.
CommandBindings.Add(new CommandBinding(commReparse,
ReparseOnExecuted));
// Command to show window.
InputGestureCollection collGest = new InputGestureCollection();
collGest.Add(new KeyGesture(Key.F7));
RoutedUICommand commShowWin =
new RoutedUICommand("Show _Window", "ShowWindow",
GetType(), collGest);
// Menu item to show window.
MenuItem itemShowWin = new MenuItem();
itemShowWin.Command = commShowWin;
itemXaml.Items.Add(itemShowWin);
// Command binding to show window.
CommandBindings.Add(new CommandBinding(commShowWin,
ShowWindowOnExecuted, ShowWindowCanExecute));
// Menu item to save as new startup document.
MenuItem itemTemplate = new MenuItem();
itemTemplate.Header = "Save as Startup _Document";
itemTemplate.Click += NewStartupOnClick;
itemXaml.Items.Add(itemTemplate);
// Insert Help on Help menu.
MenuItem itemXamlHelp = new MenuItem();
itemXamlHelp.Header = "_Help...";
itemXamlHelp.Click += HelpOnClick;
MenuItem itemHelp = (MenuItem)menu.Items[menu.Items.Count - 1];
itemHelp.Items.Insert(0, itemXamlHelp);
// New StatusBar item.
statusParse = new StatusBarItem();
status.Items.Insert(0, statusParse);
status.Visibility = Visibility.Visible;
// Install handler for unhandled exception.
// Comment out this code when experimenting with new features
// or changes to the program!
Dispatcher.UnhandledException += DispatcherOnUnhandledException;
}
// Override of NewOnExecute handler puts StartupDocument in TextBox.
protected override void NewOnExecute(object sender,
ExecutedRoutedEventArgs args)
{
base.NewOnExecute(sender, args);
string str = ((XamlCruncherSettings)settings).StartupDocument;
// Make sure the next Replace doesn’t add too much.
str = str.Replace("/r/n", "/n");
// Replace line feeds with carriage return/line feeds.
str = str.Replace("/n", "/r/n");
txtbox.Text = str; isFileDirty = false;
}
// Override of LoadSettings loads XamlCruncherSettings.
protected override object LoadSettings()
{
return XamlCruncherSettings.Load(typeof(XamlCruncherSettings),
strAppData);
}
// Override of OnClosed saves Orientation from menu item.
protected override void OnClosed(EventArgs args)
{
settingsXaml.Orientation = itemOrientation.Orientation;
base.OnClosed(args);
}
// Override of SaveSettings saves XamlCruncherSettings object.
protected override void SaveSettings()
{
((XamlCruncherSettings)settings).Save(strAppData);
}
// Handler for Tab Spaces menu item.
void TabSpacesOnClick(object sender, RoutedEventArgs args)
{
XamlTabSpacesDialog dlg = new XamlTabSpacesDialog();
dlg.Owner = this;
dlg.TabSpaces = settingsXaml.TabSpaces;
if ((bool)dlg.ShowDialog().GetValueOrDefault())
{
settingsXaml.TabSpaces = dlg.TabSpaces;
}
}
// Handler for Reparse menu item.
void ReparseOnExecuted(object sender, ExecutedRoutedEventArgs args)
{
Parse();
}
// Handlers for Show Window menu item.
void ShowWindowCanExecute(object sender, CanExecuteRoutedEventArgs args)
{
args.CanExecute = (win != null);
}
void ShowWindowOnExecuted(object sender, ExecutedRoutedEventArgs args)
{
if (win != null)
win.Show();
}
// Handler for Save as New Startup Document menu item.
void NewStartupOnClick(object sender, RoutedEventArgs args)
{
((XamlCruncherSettings)settings).StartupDocument = txtbox.Text;
}
// Help menu item.
void HelpOnClick(object sender, RoutedEventArgs args)
{
Uri uri = new Uri("pack://application:,,,/XamlCruncherHelp.xaml");
Stream stream = Application.GetResourceStream(uri).Stream;
Window win = new Window();
win.Title = "XAML Cruncher Help";
win.Content = XamlReader.Load(stream);
win.Show();
}
// OnPreviewKeyDown substitutes spaces for Tab key.
protected override void OnPreviewKeyDown(KeyEventArgs args)
{
base.OnPreviewKeyDown(args);
if (args.Source == txtbox && args.Key == Key.Tab)
{
string strInsert = new string(’ ’, tabspaces);
int iChar = txtbox.SelectionStart;
int iLine = txtbox.GetLineIndexFromCharacterIndex(iChar);
if (iLine != -1)
{
int iCol = iChar - txtbox.GetCharacterIndexFromLineIndex(iLine);
strInsert = new string(’ ’,
settingsXaml.TabSpaces - iCol % settingsXaml.TabSpaces);
}
txtbox.SelectedText = strInsert;
txtbox.CaretIndex = txtbox.SelectionStart + txtbox.SelectionLength;
args.Handled = true;
}
}
// TextBoxOnTextChanged attempts to parse XAML.
void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
if (IsSuspendParsing)
txtbox.Foreground = SystemColors.WindowTextBrush;
else
Parse();
}
// General Parse method also called for Reparse menu item.
void Parse()
{
StringReader strreader = new StringReader(txtbox.Text);
XmlTextReader xmlreader = new XmlTextReader(strreader);
try
{
object obj = XamlReader.Load(xmlreader);
txtbox.Foreground = SystemColors.WindowTextBrush;
if (obj is Window)
{
win = obj as Window;
statusParse.Content = "Press F7 to display Window";
}
else
{
win = null;
frameParent.Content = obj;
statusParse.Content = "OK";
}
}
catch (Exception exc)
{
txtbox.Foreground = Brushes.Red;
statusParse.Content = exc.Message;
}
}
// UnhandledException handler required if XAML object throws exception.
void DispatcherOnUnhandledException(object sender,
DispatcherUnhandledExceptionEventArgs args)
{
statusParse.Content = "Unhandled Exception: " + args.Exception.Message;
args.Handled = true;
}
}
}
Parse方法负责解析XAML。如果XamlReader.Load产生一个异常,处理函数会将TextBox的文字转成红色,并在状态栏显示错误信息。如果没有异常,则会将对象设定成Frame控件的Content。对于Window类型的对象要特别处理,将这样的对象保存在一个字段,按下F7之后会产生一个独立的窗口。
有时候,建立自XAML的element tree中有些东西会抛出异常。因为建立自XAML的对象是此应用程序的一部分,此异常可能会造成XAML Cruncher本身被结束,尽管并非XAML Cruncher本身的错误。为此,程序设置了一个UnhandledException事件处理函数,处理事件的方式是在状态栏显示此信息。除非程序可能会遇到不是自身产生的异常(像XAML Cruncher的例子这样),否则一般来说,程序不应该设置此事件处理函数。
让你改变tab空格数的菜单,会显示出一个小小的对话框。此对话框的layout是一个XAML文件。
XamlTabSpacesDialog.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Petzold.XamlCruncher.XamlTabSpacesDialog"
Title="Tab Spaces"
WindowStyle="ToolWindow"
SizeToContent="WidthAndHeight"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">




TextChanged="TextBoxOnTextChanged"
Margin="6,12,12,12"/>







如果你理解了前一章节的内容,那么此XAML文件应该没有让你意外的地方。Code-behind文件定义了public TabSpaces property和两个事件处理函数。
XamlTabSpacesDialog.cs
//----------------------------------------------------
// XamlTabSpacesDialog.cs (c) 2006 by Charles Petzold
//----------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
namespace Petzold.XamlCruncher
{
public partial class XamlTabSpacesDialog
{
public XamlTabSpacesDialog()
{
InitializeComponent();
txtbox.Focus();
}
public int TabSpaces
{
set { txtbox.Text = value.ToString(); }
get { return Int32.Parse(txtbox.Text); }
}
void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
int result;
btnOk.IsEnabled = (Int32.TryParse(txtbox.Text, out result) &&
result > 0 && result < 11);
}
void OkOnClick(object sender, RoutedEventArgs args)
{
DialogResult = true;
}
}
}
此类定义了一个名为TabSpaces的property,它直接存取TextBox的Text property。你会注意到get accessor调用Int32结构的静态Parse方法,却不担心会发生异常,这份自信是因为有TextChanged事件处理函数的缘故。只有静态的TryParse返回true,且输入的数字介于1和10之间,TextChanged事件处理函数才会enable OK按钮。
当用户从菜单选择Tab Spaces项目,XamlCruncher类会调用此对话框。下面是该菜单项的Click事件处理函数内完整的程序代码:
XamlTabSpacesDialog dlg = new XamlTabSpacesDialog();
dlg.Owner = this;
dlg.TabSpaces = settingsXaml.TabSpaces;
if ((bool)dlg.ShowDialog().GetValueOrDefault())
{
settingsXaml.TabSpaces = dlg.TabSpaces;
}
设定Owner property,可以确保XAML所指定的WindowStartupLocation发挥作用。当用户按下Cancel关闭对话框,程序却存取TabSpaces property,就可能会造成麻烦发生。只有在按下OK按钮时,TabSpaces才保证不会发生异常。
改变TextBox与Frame方向(orientation)的菜单项,具有自己的类,以将4种可行方向用符号表示,并附带图片。当用户选取新的方向时,此类也必须重新安排Grid中element的位置。
XamlOrientationMenuItem.cs
//--------------------------------------------------------
// XamlOrientationMenuItem.cs (c) 2006 by Charles Petzold
//--------------------------------------------------------
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Petzold.XamlCruncher
{
class XamlOrientationMenuItem : MenuItem
{
MenuItem itemChecked;
Grid grid;
TextBox txtbox;
Frame frame;
// Orientation public property of type Dock.
public Dock Orientation
{
set
{
foreach (MenuItem item in Items)
if (item.IsChecked = (value == (Dock)item.Tag))
itemChecked = item;
}
get
{
return (Dock)itemChecked.Tag;
}
}
// Constructor requires three arguments.
public XamlOrientationMenuItem(Grid grid, TextBox txtbox, Frame frame)
{
this.grid = grid;
this.txtbox = txtbox;
this.frame = frame;
Header = "_Orientation";
for (int i = 0; i < 4; i++)
Items.Add(CreateItem((Dock)i));
(itemChecked = (MenuItem) Items[0]).IsChecked = true;
}
// Create each menu item based on Dock setting.
MenuItem CreateItem(Dock dock)
{
MenuItem item = new MenuItem();
item.Tag = dock;
item.Click += ItemOnClick;
item.Checked += ItemOnCheck;
// Two text strings that appear in menu item.
FormattedText formtxt1 = CreateFormattedText("Edit");
FormattedText formtxt2 = CreateFormattedText("Display");
double widthMax = Math.Max(formtxt1.Width, formtxt2.Width);
// Create a DrawingVisual and a DrawingContext.
DrawingVisual vis = new DrawingVisual();
DrawingContext dc = vis.RenderOpen();
// Draw boxed text on the visual.
switch (dock)
{
case Dock.Left: // Edit on left, display on right.
BoxText(dc, formtxt1, formtxt1.Width, new Point(0, 0));
BoxText(dc, formtxt2, formtxt2.Width,
new Point(formtxt1.Width + 4, 0));
break;
case Dock.Top: // Edit on top, display on bottom.
BoxText(dc, formtxt1, widthMax, new Point(0, 0));
BoxText(dc, formtxt2, widthMax,
new Point(0, formtxt1.Height + 4));
break;
case Dock.Right: // Edit on right, display on left.
BoxText(dc, formtxt2, formtxt2.Width, new Point(0, 0));
BoxText(dc, formtxt1, formtxt1.Width,
new Point(formtxt2.Width + 4, 0));
break;
case Dock.Bottom: // Edit on bottom, display on top.
BoxText(dc, formtxt2, widthMax, new Point(0, 0));
BoxText(dc, formtxt1, widthMax,
new Point(0, formtxt2.Height + 4));
break;
}
dc.Close();
// Create Image object based on Drawing from visual.
DrawingImage drawimg = new DrawingImage(vis.Drawing);
Image img = new Image();
img.Source = drawimg;
// Set the Header of the menu item to the Image object.
item.Header = img;
return item;
}
// Handles the hairy FormattedText arguments.
FormattedText CreateFormattedText(string str)
{
return new FormattedText(str, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(SystemFonts.MenuFontFamily, SystemFonts.MenuFontStyle,
SystemFonts.MenuFontWeight, FontStretches.Normal),
SystemFonts.MenuFontSize, SystemColors.MenuTextBrush);
}
// Draws text surrounded by a rectangle.
void BoxText(DrawingContext dc, FormattedText formtxt,
double width, Point pt)
{
Pen pen = new Pen(SystemColors.MenuTextBrush, 1);
dc.DrawRectangle(null, pen,
new Rect(pt.X, pt.Y, width + 4, formtxt.Height + 4));
double X = pt.X + (width - formtxt.Width) / 2;
dc.DrawText(formtxt, new Point(X + 2, pt.Y + 2));
}
// Check and uncheck items when clicked.
void ItemOnClick(object sender, RoutedEventArgs args)
{
itemChecked.IsChecked = false;
itemChecked = args.Source as MenuItem;
itemChecked.IsChecked = true;
}
// Change the orientation based on the checked item.
void ItemOnCheck(object sender, RoutedEventArgs args)
{
MenuItem itemChecked = args.Source as MenuItem;
// Initialize the 2nd and 3rd rows and columns to zero.
for (int i = 1; i < 3; i++)
{
grid.RowDefinitions[i].Height = new GridLength(0);
grid.ColumnDefinitions[i].Width = new GridLength(0);
}
// Initialize the cell of the TextBox and Frame to zero.
Grid.SetRow(txtbox, 0);
Grid.SetColumn(txtbox, 0);
Grid.SetRow(frame, 0);
Grid.SetColumn(frame, 0);
// Set row and columns based on the orientation setting.
switch ((Dock)itemChecked.Tag)
{
case Dock.Left: // Edit on left, display on right.
grid.ColumnDefinitions[1].Width = GridLength.Auto;
grid.ColumnDefinitions[2].Width =
new GridLength(100, GridUnitType.Star);
Grid.SetColumn(frame, 2);
break;
case Dock.Top: // Edit on top, display on bottom.
grid.RowDefinitions[1].Height = GridLength.Auto;
grid.RowDefinitions[2].Height =
new GridLength(100, GridUnitType.Star);
Grid.SetRow(frame, 2);
break;
case Dock.Right: // Edit on right, display on left.
grid.ColumnDefinitions[1].Width = GridLength.Auto;
grid.ColumnDefinitions[2].Width =
new GridLength(100, GridUnitType.Star);
Grid.SetColumn(txtbox, 2);
break;
case Dock.Bottom: // Edit on bottom, display on top.
grid.RowDefinitions[1].Height = GridLength.Auto;
grid.RowDefinitions[2].Height =
new GridLength(100, GridUnitType.Star);
Grid.SetRow(txtbox, 2);
break;
}
}
}
}
XamlCruncher类的构造函数也会存取顶层的Help菜单项,并加入一个Help子菜单。此菜单项显示一个窗口,内容包含对程序和新菜单项的简单描述。这些年来,Help文件可能用Rich Text Format(RTF)格式保存,或者用流行的HTML格式保存。
然而,让我们展示一下新技术,使用一个FlowDocument对象来写Help文件,RichTextBox内部正是使用FlowDocument对象。我用更早期版本的XAML Cruncher写出下面的文件。这个文件应该相当容易理解,因为里面都是完整的英文单词,像是Paragraph、Bold、Italic。
XamlCruncherHelp.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
TextAlignment="Left">

LineHeight="24">
XAML Cruncher


© 2006 by Charles Petzold


Introduction


XAML Cruncher is a sample program from Charles Petzold’s book

Applications = Code + Markup:
A Guide to the Microsoft Windows Presentation Foundation

published by Microsoft Press in 2006.
XAML Cruncher provides a convenient way to learn about and experiment
with XAML, the Extensible Application Markup Language.


XAML Cruncher consists of an Edit section (in which you enter and edit
a XAML document) and a Display section that shows the object created
from the XAML. If the XAML document has errors, the text is displayed
in red and the status bar indicates the problem.


Most of the interface and functionality of the edit section of
XAML Cruncher is based on Windows Notepad.
The Xaml menu provides additional features.


Xaml Menu


The Orientation menu item lets you choose whether you
want the Edit and Display sections of XAML Cruncher arranged
horizontally or vertically.


The Tab Spaces menu item displays a dialog box that lets
you choose the number of spaces you want inserted when you press the
Tab key. Changing this item does not change any indentation
already in the current document.


There are times when your XAML document will be so complex that it
takes a little while to convert it into an object. You may want to
Suspend Parsing by checking this item on the
Xaml menu.


If you’ve suspended parsing, or if you want to reparse the XAML file,
select Reparse from the menu or press F6.


If the root element of your XAML is Window ,
XAML Cruncher will not be able to display the Window
object in its own window.
Select the Show Window menu item or press F7 to view
the window.


When you start up XAML Cruncher (and whenever you select
New from the File menu), the Edit window
displays a simple startup document.
If you want to use the current document as the startup document,
select the Save as Startup Document item.


此文件必须在Visual Studio中被指定为资源Resource。Visual Studio默认会将它指定成Page。它必须是Resource,因为XamlCruncher的程序代码是以资源的方式对待它的。HelpOnClick事件处理函数先为此资源取得一个URI对象,然后建立一个Stream:
Uri uri = new Uri("pack://application:,,,/XamlCruncherHelp.xaml");
Stream stream = Application.GetResourceStream(uri).Stream;
此方法然后建立一个Window,设定Title,将Content property设定成XamlReader.Load所返回来的FlowDocument对象(传入引用此资源的Stream对象):
Window win = new Window();
win.Title = "XAML Cruncher Help";
win.Content = XamlReader.Load(stream);
win.Show();
有一些其他的做法。其中一种是建立一个Frame控件,并将其Source property直接设定为此Uri对象:
Frame frame = new Frame();
frame.Source = new Uri("pack://application:,,/XamlCruncherHelp.xaml");
现在,建立一个Window,将其内容设定为此Frame:
Window win = new Window();
win.Title = "XAML Cruncher Help";
win.Content = frame;
win.Show();
第三种做法:首先,建立一个XAML文件,用来定义此Help窗口。可以将此文件命名为XamlHelpDialog.xaml:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="XAML Cruncher Help"
x:Class="Petzold.XamlCruncher.XamlHelpDialog">


请注意用来设定Frame控件Source property的简化语法。现在HelpOnClick方法减少到只剩下这3条语句:
XamlHelpDialog win = new XamlHelpDialog();
win.InitializeComponent();
win.Show();
在建立XAML所定义的XamlHelpDialog对象之后,此方法会调用InitializeComponent(这个工作一般是由code-behind文件负责的)以及Show。此做法有趣的一点在于,负责定义FlowDocument的XamlCruncherHelp.xaml的Build Action可以是Resource,也可以是Page。如果是Page的话,此XAML会以编译过的BAML文件形式,放在.EXE文件中。
不管你如何显示Help文件,XAML Cruncher现在已经完全具备一切,而且可以使用了。在本章和后续的许多章节中,我会向你展示可以用XAML Cruncher(或类似的程序)建立并执行的独立XAML文件。
让我们从一个简单的XAML文件开始,这是XAML Cruncher默认建立的内容:

此静态的XamlReader.Load方法解析此文字内容,并建立Button类型的对象。为了方便起见,我会将XamlReader.Load方法称为解析器(parser),因为它解析XAML并以此建立一个或多个对象。
程序员可能问的第一个问题是:解析器怎么知道要使用什么类来建立此特定的Button对象?毕竟,.NET有3种Button类。有Windows Forms的Button和ASP.NET的Button。虽然此XAML文件包含两个XML命名空间,它并没有包含像是System.Windows.Controls这样的CLR命名空间。此XAML也没有包含对PresentationFramework.dll组件的引用(此组件定义了System.Windows.Controls.Button类)。为何解析器不要求提供完整名称(含命名空间)的类或using指示符呢?
其中一种答案是:WPF应用程序没有引用Windows Forms组件或ASP.NET组件,而是引用了PresentationFramework.dll组件,因为此组件是定义XamlReader类的组件。但是就算WPF应用程序引用了System.Windows.Forms.dll或System.Web.dll,而且这些组件被此应用程序所加载,此解析器仍然知道该使用哪个Button类。
此迷团的解答就在PresentationFramework组件内。此组件包含许多自定义attribute。(应用程序可以调用此Assembly对象的GetCustomAttributes,查询出这些attribute。)有数个attribute是属于XmlnsDefinitionAttribute,而且此类包含两个重要的property,名为XmlNamespace与ClrNamespace。PresentationFramework的一个XmlnsDefinitionAttribute对象将XmlNamespace设定成字符串“http://schemas.microsoft.com/winfx/2006/xaml/presentation”,并且将ClrNamespace property设定成字符串“System.Windows.Controls”。语法是这样的:
[assembly:XmlnsDefinition
("http://schemas.microsoft.com/winfx/2006/xaml/presentation",
"System.Windows.Controls")]
其他的XmlnsDefinition attribute将此相同的XML命名空间与其他CLR命名空间建立关联,这些CLR命名空间包括了System.Windows、System.Windows.Controls.Primitives、System.Windows.Input、System.Windows.Shapes等。
此XAML解析器会检查应用程序所加载的所有组件的XmlnsDefinition attribute(如果有的话)。如果这些attribute内的XML命名空间符合XAML文件内的XML命名空间,当在这些组件中搜寻Button类时,解析器就会确定是哪个CLR命名空间。
如果程序引用了另外的组件,而该组件包含一个类似的XmlnsDefinition,其XML命名空间一样,但却是完全不同的Button类,那么此解析器应该会遇到问题。但是这不可能发生,除非某人建立一个组件,使用微软的XML命名空间或微软内部有人犯了大错。
让我们设定Width property,为XAML Cruncher内的button指定一个明确的宽度:

此解析器可以通过reflection得知Button需要Width property。此解析器也可以得知此property是double类型,或者CLR类型的System.Double。然而,对于XML attribute来说,Width的值是一个字符串。此解析器必须将该字符串转换为double类型的对象。这听起来好像没什么,事实上,Double结构就包含了一个静态的Parse方法,可以将字符串转成数字。
然而,一般情况下,此转换并不是简单的工作,特别是,你也可以在指定宽度时使用英寸当单位:
Width="1.5in"
你可以在数字和“in”之间放上空格,而且还可以使用大写或小写字母:
Width="1.5 IN"
也可以使用科学记数法:
Width="15e-1in"
指定非数字(Not a Number)时用NaN(要遵照这样的大小写):
Width="NaN"
虽然语义上不适合Width property,但某些double attribute允许“Infinity”或“-Infinity”。当然,你也可以采用公制:
Width="3.81cm"
或者,如果你具有印刷的知识背景,你可以使用打印机的point:
Width="108pt"
Double.Parse方法允许科学记数法、NaN、Infinity,但是不允许“in”、“cm”、或“pt”,这些必须另外处理。
当XAML解析器遇到double类型的property,会从System.ComponentModel命名空间找出DoubleConverter类。这是诸多的转换器(converter)类之一。它们全都是继承自TypeConverter,而且包含一个名为ConvertFromString的方法,该方法内部可能(以此例来说)会使用Double.Parse方法来进行转换。
类似地,当你设定Margin attribute(类型为Thickness)的时候,此解析器会找出System.Windows命名空间的ThicknessConverter类。此转换器允许你设定一个值,同时用在4边:
Margin="48"
或者指定两个值,第一个用在左右,第二个用在上下:
Margin="48 96"
你可以将这两个数字用一个空白或逗号分开。你也可以使用4个数字,分别表示左、上、右、下:
Margin="48 96 24 192"
如果你使用“in”或“cm”或“pt”,那么数字和单位之间不可以有空格:
Margin="1.27cm 96 18pt 2in"
当你在Visual Studio的编辑器中输入XAML时,它会应用某些比实际的解析器更加严格的规则,如果你的XAML不符合规则,就会警告你。比方说,定义Thickness对象,Visual Studio希望你用逗号分开这些值。
如果是布尔值,使用“true”或“false”,随便你大小写怎么混用:
IsEnabled="FaLSe"
然而Visual Studio希望你使用“True”和“False”。
对于值必须为枚举成员的property来说,EnumConverter类要求你在设定attribute时,使用枚举成员本身(不需冠以枚举名称):
HorizontalAlignment="Center"
你不要将FontStretch、FontStyle、FontWeight property设定成枚举成员,而要将它们设定成FontStretches、FontStyles、FontWeights等静态类的property。FontStretchConverter、FontStyleConverter、FontWeightConverter类让你直接使用那些静态的property。你将FontFamily设定成一个字符串,将FontSize设定成一个double:
FontFamily="Times New Roman"
FontSize="18pt"
FontWeight="Bold"
FontStyle="Italic"
让我们再来做一些不一样的事。下面的XAML文件名为Star.xaml,呈现出一个五角星形:
Star.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Points="144 48, 200 222, 53 114, 235 114, 88 222"
Fill="Red"
Stroke="Blue"
StrokeThickness="5" />
Polygon包含一个名为Points的property,类型为PointCollection。幸好有PointCollectionConverter的存在,你可以将这些点指定为一连串的X坐标和Y坐标,这些数字可以通过空格或逗号分隔。某些人在X和Y坐标之间用逗号分隔,在点和点之间用空格分隔,有些人(包括我)喜欢用逗号分隔点。
BrushConverter类让你使用Brushes类的静态成员来指定颜色,当然,你也可以使用十六进制的RGB颜色值:
Fill="#FF0000"
下面是相同的颜色,但是alpha channel为128(也就是半透明):
Fill="#80FF0000"
你也可以使用scRGB分量的红、绿、蓝颜色值,alpha channel置于最前面。半透明的红表达如下:
Fill="sc#0.5,1,0,0"
现在,不将Fill property设定成SolidColorBrush类型的对象,让我们将Fill property设定成LinearGradientBrush对象。
忽然间,我们好像撞到墙壁了。我们要如何用文字字符串表达整个LinearGradientBrush,以便指定给Fill property?SolidColorBrush只需要一个颜色值,但渐变画刷(gradient brush)需要至少两个颜色值和两个渐变点。标记(markup)的限制似乎已经出现了。
你确实可以在XAML中指定LinearGradientBrush,为了要了解它是如何完成的,让我们先看看另一种语法,也可以将Fill property设定成红色实心画刷。首先,将没有内容的Polygon对象empty tag,用明确的end tag取代:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Points="144 48, 200 222, 53 114, 235 114, 88 222"
Fill="Red"
Stroke="Blue"
StrokeThickness="5">

接下来,将Fill attribute从Polygon tag中移除,用名为Polygon.Fill的child element取而代之。该element的内容是“Red”:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Points="144 48, 200 222, 53 114, 235 114, 88 222"
Stroke="Blue"
StrokeThickness="5">

Red


让我们将某些术语固定下来。许多XAML element关系到类和结构,并且导致对象的建立。这些element称为object element:

Element常常会包含attribute,以设定这些对象的property。这些attribute称为property attribute:
Fill="Red"
用涉及child element的语法来指定property也行。这称为property element:

Red

Property element的特征是在element名称和property名称之间有一个点。Property element没有attribute。你绝对不会看到下面的描述方式:


...

甚至连XML命名空间的声明也不能出现在这里。如果你试着在XAML Cruncher中对一个property element设定attribute,你会得到信息“无法在property element中设定property”,这是当你尝试这样做时XamlReader.Load所抛出异常的信息。
Property element的内容必须可以被转成此property的类型。Polygon.Fill property element是Fill property,类型是Brush,所以此property element的内容必须可以被转成Brush:

Red

这样也行:

#FF0000

你可以用下面的语法(要打比较多的字),让Polygon.Fill的内容更明确是Brush:


Red


现在Polygon.Fill property element的内容是一个Brush object element,也就是文字字符串“Red”。此内容可以被转成SolidColorBrush类型的对象,所以你可以将Polygon.Fill property element写成这样:


Red


SolidColorBrush具有一个名为Color的property,而且ColorConverter类允许和Brush转换器(converter)相同的转换,所以你可以使用一个property attribute设定SolidColorBrush的Color property:




然而,你现在无法使用Brush取代SolidColorBrush,因为Brush不具有名为Color的property。
因为SolidColorBrush不具有content,你可以使用empty-element语法写tag:



或者,你可以使用property element语法,将SolidColorBrush的Color property分离出来:



Red



SolidColorBrush类的Color property是Color类型的对象,所以你可以明确地使用一个object element作为SolidColorBrush.Color的内容:




Red




Color有4个property,分别名为A、R、G、B,都是byte类型。你可以在Color tag中设定这些,不管是用十进制或十六进制的语法都行:








别忘了,你无法在SolidColorBrush.Color tag中设定这4个peoperty,因为你“无法在property element中设定property”,还记得这个异常信息吧!
因为Color element现在没有内容,你可以用empty-element的语法写它:







或者,你可以将Color的一个或多个attribute分离出来:





#FF





Color的R property类型是Byte,这是定义在System命名空间中的结构。你甚至可以在XAML中使用Byte element,让R的数据类型更明显。然而,System命名空间和XAML文件顶端的两个XML命名空间都没有关联,为了要使用Byte结构,你需要另一个XML命名空间的声明。让我们将System命名空间和s前缀产生关联:
xmlns:s="clr-namespace:System;assembly=mscorlib"
请注意,引号内的字符串一开始是clr-namespace,后面接着一个冒号和一个CLR命名空间,彷佛你正在将此前缀和程序中的一个CLR命名空间关联起来。(如同第19章UseCustomClass项目所展示的那样。)因为System命名空间的类与结构是位于外部组件(external assembly),此信息相当重要,需要跟在后面。在CLR命名空间之后,加上一个分号,然后使用“assembly”这个字,再用一个等号,最后是组件名称。请注意,冒号是用来分隔clr-namespace和CLR命名空间的,但是等号是用来分隔assembly和组件名称的。这里的概念是,冒号之前的开头部分,可以比拟为传统命名空间声明的“http:”部分。
该声明需要放进Byte element本身,或者放到Byte element的父亲。让我们将它放在Color element中(这么做的理由,稍后你就知道了):




A="255" G="0" B="0">


#FF






你可以极端地将Color的所有4个property分离出来:






255




255




0




0






如果新的命名空间声明出现在第一个Byte element中,这是行不通的。命名空间的声明会被应用到内部嵌套的所有element,所以放在Color中才对。
我希望你不要再提出更多需求了,因为我们已经让此XAML变得超出必要地复杂。然而,此语法所展示的做法,适合用来为Fill property定义一个渐变画刷。
LinearGradientBrush具有两个property,名为StartPoint与EndPoint。默认的情况下,这些property都是在一个相对于要着色的对象的坐标系统中。第三个重要的property是GradientStops,类型为GradientStopCollection,这是GradientStop对象的collection,用来指定颜色。
Polygon.Fill property element的内容必须是Brush类型。LinearGradientBrush类型的object element可以满足此条件:


...


StartPoint和EndPoint property都很简单,可以定义为LinearGradientBrush start tag的attribute property:


...


然而,GradientStops property必须变成一个property element:



...



然而,GradientStops property必须变成一个property element:




...




GradientStopCollection类实现了IList接口,这使得我们可以将它的成员简单地表达成孩子。请注意,每个GradientStop element都可以用empty-element语法,相当容易地表达:









这可以写得更简单一点。没有必要将LinearGradientBrush.GradientStops property element或GradientStopCollection object element明确地包含进来。可以将它们移除:







就这样!Polygon.Fill property是LinearGradientBrush类型的对象。LinearGradientBrush具有StartPoint、EndPoint、GradientStops这3个property。而且LinearGradientBrush具有3个GradientStop对象的collection。GradientStop具有Offset与Color property。
下面是一个使用RadialGradientBrush的星形:
RadialGradientStar.cs

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Points="144 48, 200 222, 53 114, 235 114, 88 222"
Stroke="Blue"
StrokeThickness="5">









让我们回到Button。这个XAML文件展示了Button的3个property,它们被当作attribute来设定,其中包括Content property:

你可以让Foreground attribute成为property element,做法就像这样:

或者你可以让FontSize attribute成为property element,像这样:

或者Foreground与FontSize都可以是property element:

让Content property作为property element,也是可以的:

然而,在这种情况下,Button.Content tag可以不必写:

事实上,你可以将此内容和property element混合。我在此XAML文件中加入一些空白行,好让它更容易阅读:

对于Button的每个property,你可以将此property作为attribute,在Button start tag中作设定,也可以使用property element,将property的值设定为此element的孩子,Content property除外。Content property很特别,因为你可以直接将Content property的值作为Button element的孩子,而不需要使用property element。
为什么Content会如此特别?
你能够配合XAML使用的每个类,潜在地都具有一个property,被特别地识别为content property。对于按钮来说,此content property就是Content。当定义某个类的时候,可以使用ContentPropertyAttribute说明某个property是content property。(ContentProperty- Attribute定义在System.Windows.Serialization命名空间)。在PresentationFramework.dll源码中,Button类的定义可能是这样的:
[ContentProperty("Content")]
public class Button: ButtonBase
{
...
}
或者Button是从ContentControl继承到ContentProperty attribute的设定。
另一方面,定义StackPanel时,也使用了ContentProperty attribute,看起来应该是这样:
[ContentProperty("Children")]
public class StackPanel: Panel
{
...
}
因为有ContentProperty attribute,StackPanel element的孩子就可以成为StackPanel的孩子:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">


TextBlock in the middle



LinearGradientBrush与RadialGradientBrush的content property是GradientStops。
TextBlock类的content property是Inlines集合,这是Inline对象的collection。Run类是Inline的后代,而且Run的content property是Text,而Text的类型是string。这一连串的关系结合起来,你可以用下面的做法来定义TextBlock的内容,这方便多了:

This is italic text and this is bold text

Italic和Bold类的content property也都是Inline。此部分的XAML可以写成引用Text property,因为Italic与Bold都从Span继承到Text:

This is text and this is text

因为在写XAML的时候,content property很重要,所以你可能会想取得一份清单,列出具有ContentProperty attribute的所有类,以及它们的content property。下面的console程序可以帮助你达到此目的:
DumpContentPropertyAttributes.cs
//--------------------------------------------------------------
// DumpContentPropertyAttributes.cs (c) 2006 by Charles Petzold
//--------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;
public class DumpContentPropertyAttributes
{
[STAThread]
public static void Main()
{
// Make sure PresentationCore and PresentationFramework are loaded.
UIElement dummy1 = new UIElement();
FrameworkElement dummy2 = new FrameworkElement();
// SortedList to store class and content property.
SortedList listClass = new SortedList ();
// Formatting string.
string strFormat = "{0,-35}{1}";
// Loop through the loaded assemblies.
foreach (AssemblyName asmblyname in
Assembly.GetExecutingAssembly().GetReferencedAssemblies())
{
// Loop through the types.
foreach (Type type in Assembly.Load(asmblyname).GetTypes())
{
// Loop through the custom attributes.
// (Set argument to ’false’ for non-inherited only!)
foreach (object obj in type.GetCustomAttributes(
typeof(ContentPropertyAttribute), true))
{
// Add to list if ContentPropertyAttribute.
if (type.IsPublic && obj as ContentPropertyAttribute != null)
listClass.Add(type.Name,
(obj as ContentPropertyAttribute).Name);
}
}
}
// Display the results.
Console.WriteLine(strFormat, "Class", "Content Property");
Console.WriteLine(strFormat, "-----", "----------------");
foreach (string strClass in listClass.Keys)
Console.WriteLine(strFormat, strClass, listClass[strClass]);
}
}
某些element(最值得注意的是DockPanel和Grid)具有attached property,你可以在C# 程序代码中对它们使用下面的语法:
DockPanel.SetDock(btn, Dock.Top);
此程序代码指出你想要btn控件被dock在DockPanel上方。如果btn不是DockPanel的孩子,则此调用不会发生作用。在第8章我们说过,调用静态的SetDock方法,等同于:
btn.SetValue(DockPanel.DockProperty, Dock.Top);
在XAML中,你使用这样的语法:

这里有一个独立的XAML文件,有点像第6章的DockAroundTheBlock程序,但是更加简单一些。
AttachedPropertiesDemo.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">






Grid在XAML中很好用,因为row与column的定义不像C# 版本一样繁琐。每个RowDefinition和ColumnDefinition都可以在Grid.RowDefinitions与Grid.Column- Definitions property element内占据一行。下面是另一个独立的XAML文件。
SimpleGrid.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">














Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" />




如果你对于Height与Width默认的值“1*”(这是XAML的语法)感到满意,不想改变它,那么你甚至可以把RowDefinition和ColumnDefinition elements写成这样:

Attached property不是唯一可以包含“点”的attribute。你也可以定义一个element的property,让attribute由类名称后面接着property名称所组成。这里的类名称可以和此attribute出现的地方的element相同,或者是一个祖先类,或者是拥有相同的dependency property的类。比方说,这些都是Button element的有效attribute:
Button.Foreground="Blue"
TextBlock.FontSize="24pt"
FrameworkElement.HorizontalAlignment="Center"
ButtonBase.VerticalAlignment="Center"
UIElement.Opacity="0.5"
在某些情况下,你可以在一个element中使用包含一个class名称和一个property的attribute,而且此property在这里没有被定义。这里有一个例子:
PropertyInheritance.xaml


HorizontalAlignment="Center"
TextBlock.FontSize="16pt"
TextBlock.Foreground="Blue" >

Just a TextBlock



此XAML文件在一个StackPanel element设定FontSize与Foreground property,此类不具有这些property,所以这些property必须在前面冠以定义这些property的类。这些attribute的结果是使得TextBlock和Button都具有16 pt的font size,而只有TextBlock的前景神奇地变成了蓝色。
你也可以使用attribute搭配类或事件名称,来为某个routed事件设定处理函数。此处理函数影像所有的孩子element。RoutedEventDemo工程包含两个文件:RoutedEventDemo.xaml与RoutedEventDemo.cs。此XAML文件在ContextMenu element内包含一个attribute:MenuItem.Click。此处理函数会用于组成TextBlock的context menu的MenuItem element。
RoutedEventDemo.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Petzold.RoutedEventDemo.RoutedEventDemo"
Title="Routed Event Demo">

FontSize="24pt"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ToolTip="Right click to display context menu">
TextBlock with Context Menu


[6]











C# 文件包含该事件的处理函数。触发该事件的MenuItem对象是RoutedEventArgs对象的Source property。请注意处理函数使用静态的ColorConverter.ConvertFromString方法,将MenuItem文字转成一个Color对象。
RoutedEventDemo.cs
//--------------------------------------------------
// RoutedEventDemo.cs (c) 2006 by Charles Petzold
//--------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.RoutedEventDemo
{
public partial class RoutedEventDemo : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new RoutedEventDemo());
}
public RoutedEventDemo()
{
InitializeComponent();
}
void MenuItemOnClick(object sender, RoutedEventArgs args)
{
string str = (args.Source as MenuItem).Header as string;
Color clr = (Color)ColorConverter.ConvertFromString(str);
txtblk.Foreground = new SolidColorBrush(clr);
}
}
}
你已经看过数个例子,体会到何时element或者attribute可以包含一个点。下面是总结:
如果element名称没有包含一个点,就一定是类或结构的名称:

如果element名称包含一个点,此element名称是由类(或结构)名称后面接着该类的一个property所组成。这就是property element:

...

此特定的element必须以Button element孩子的姿态出现,而且start tag不可以有attribute。此element必须包含可以转成该property类型的内容(比方说,文字字符串“Red”或“#FF0000”)或相同类型的child element(比方说,Brush或任何继承自Brush的类)。
Attribute名称通常不包含点:
< ... Background="Red" ... >
这些attribute对应着它们所出现地方的element的property。当attribute包含点的时候,它有可能是attached property:
< ... DockPanel.Dock="Left" ... >
此特定的attached property通常会出现在DockPanel的孩子中,但并非一定如此。如果此attached property出现在一个element,而此element不是DockPanel的孩子,那么就直接被忽略。
具有“点”的attribute也可以是routed输入事件:
< ... MenuItem.Click="MenuItemOnClick" ... >
此attribute更有可能出现在具有多个MenuItem element作为孩子的element内,而不是MenuItem element内(因为那样的话,attribute名称只需要写Click就行)。“点”也有可能出现在想要让孩子继承的property的定义中:
< ... TextBlock.FontSize="24pt" ... >
我认为你会同意,在许多例子中,XAML会比等价的C# 程序代码更精要,而且更能表现出层次结构,像是窗口上的面板与控件的layout。但是markup一般比程序语言受到更多限制,主要是因为缺乏流程控制(flow control)。即使只是简单的变量共享,在XAML中似乎都不太可能。然而,从下一章开始,你会看到XAML具有一些特性,可以弥补它的不足之处。

本文转自
http://book.csdn.net/bookfiles/591/10059119379.shtml
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值