C#用户自定义控件(含源代码)-透明文本框

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

using System.Drawing.Imaging;

namespace ZBobb
{
    /// <summary>
    /// AlphaBlendTextBox: A .Net textbox that can be translucent to the background.
    /// (C) 2003 Bob Bradley / [email protected]
    /// </summary>
 

    public class AlphaBlendTextBox : System.Windows.Forms.TextBox
    {
        #region private variables

        private uPictureBox myPictureBox;
        private bool myUpToDate = false;
        private bool myCaretUpToDate = false;
        private Bitmap myBitmap;
        private Bitmap myAlphaBitmap;

        private int myFontHeight = 10;

        private System.Windows.Forms.Timer myTimer1;

        private bool myCaretState = true;

        private bool myPaintedFirstTime = false;

        private Color myBackColor = Color.White;
        private int myBackAlpha = 10;

        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        #endregion // end private variables


        #region public methods and overrides

        public AlphaBlendTextBox()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();
            // TODO: Add any initialization after the InitializeComponent call

            this.BackColor = myBackColor;

            this.SetStyle(ControlStyles.UserPaint, false);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);


            myPictureBox = new uPictureBox();
            this.Controls.Add(myPictureBox);
            myPictureBox.Dock = DockStyle.Fill;
        }


        protected override void OnResize(EventArgs e)
        {

            base.OnResize(e);
            this.myBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);//(this.Width,this.Height);
            this.myAlphaBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);//(this.Width,this.Height);
            myUpToDate = false;
            this.Invalidate();
        }


        //Some of these should be moved to the WndProc later

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            myUpToDate = false;
            this.Invalidate();
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
            myUpToDate = false;
            this.Invalidate();

        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);
            myUpToDate = false;
            this.Invalidate();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            this.Invalidate();
        }

        protected override void OnGiveFeedback(GiveFeedbackEventArgs gfbevent)
        {
            base.OnGiveFeedback(gfbevent);
            myUpToDate = false;
            this.Invalidate();
        }


        protected override void OnMouseLeave(EventArgs e)
        {
            //found this code to find the current cursor location
            //at http://www.syncfusion.com/FAQ/WinForms/FAQ_c50c.asp#q597q

            Point ptCursor = Cursor.Position;

            Form f = this.FindForm();
            ptCursor = f.PointToClient(ptCursor);
            if (!this.Bounds.Contains(ptCursor))
                base.OnMouseLeave(e);
        }


        protected override void OnChangeUICues(UICuesEventArgs e)
        {
            base.OnChangeUICues(e);
            myUpToDate = false;
            this.Invalidate();
        }


        //--
        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            myCaretUpToDate = false;
            myUpToDate = false;
            this.Invalidate();


            myTimer1 = new System.Windows.Forms.Timer(this.components);
            myTimer1.Interval = (int)win32.GetCaretBlinkTime(); //  usually around 500;

            myTimer1.Tick += new EventHandler(myTimer1_Tick);
            myTimer1.Enabled = true;

        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            myCaretUpToDate = false;
            myUpToDate = false;
            this.Invalidate();

            myTimer1.Dispose();
        }

        //--  

        protected override void OnFontChanged(EventArgs e)
        {
            if (this.myPaintedFirstTime)
                this.SetStyle(ControlStyles.UserPaint, false);

            base.OnFontChanged(e);

            if (this.myPaintedFirstTime)
                this.SetStyle(ControlStyles.UserPaint, true);


            myFontHeight = GetFontHeight();


            myUpToDate = false;
            this.Invalidate();
        }

        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            myUpToDate = false;
            this.Invalidate();
        }


        protected override void WndProc(ref Message m)
        {

            base.WndProc(ref m);

            // need to rewrite as a big switch

            if (m.Msg == win32.WM_PAINT)
            {
                myPaintedFirstTime = true;

                if (!myUpToDate || !myCaretUpToDate)
                    GetBitmaps();
                myUpToDate = true;
                myCaretUpToDate = true;

                if (myPictureBox.Image != null) myPictureBox.Image.Dispose();
                myPictureBox.Image = (Image)myAlphaBitmap.Clone();

            }

            else if (m.Msg == win32.WM_HSCROLL || m.Msg == win32.WM_VSCROLL)
            {
                myUpToDate = false;
                this.Invalidate();
            }

            else if (m.Msg == win32.WM_LBUTTONDOWN
             || m.Msg == win32.WM_RBUTTONDOWN
             || m.Msg == win32.WM_LBUTTONDBLCLK
                //  || m.Msg == win32.WM_MOUSELEAVE  ///****
             )
            {
                myUpToDate = false;
                this.Invalidate();
            }

            else if (m.Msg == win32.WM_MOUSEMOVE)
            {
                if (m.WParam.ToInt32() != 0)  //shift key or other buttons
                {
                    myUpToDate = false;
                    this.Invalidate();
                }
            }



            //System.Diagnostics.Debug.WriteLine("Pro: " + m.Msg.ToString("X"));

        }


        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                //this.BackColor = Color.Pink;
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #endregion  //end public method and overrides


        #region public property overrides

        public new BorderStyle BorderStyle
        {
            get { return base.BorderStyle; }
            set
            {
                if (this.myPaintedFirstTime)
                    this.SetStyle(ControlStyles.UserPaint, false);

                base.BorderStyle = value;

                if (this.myPaintedFirstTime)
                    this.SetStyle(ControlStyles.UserPaint, true);

                this.myBitmap = null;
                this.myAlphaBitmap = null;
                myUpToDate = false;
                this.Invalidate();
            }
        }

        public new Color BackColor
        {
            get
            {
                return Color.FromArgb(base.BackColor.R, base.BackColor.G, base.BackColor.B);
            }
            set
            {
                myBackColor = value;
                base.BackColor = value;
                myUpToDate = false;
            }
        }
        public override bool Multiline
        {
            get { return base.Multiline; }
            set
            {
                if (this.myPaintedFirstTime)
                    this.SetStyle(ControlStyles.UserPaint, false);

                base.Multiline = value;

                if (this.myPaintedFirstTime)
                    this.SetStyle(ControlStyles.UserPaint, true);

                this.myBitmap = null;
                this.myAlphaBitmap = null;
                myUpToDate = false;
                this.Invalidate();
            }
        }


        #endregion    //end public property overrides


        #region private functions and classes

        private int GetFontHeight()
        {
            Graphics g = this.CreateGraphics();
            SizeF sf_font = g.MeasureString("X", this.Font);
            g.Dispose();
            return (int)sf_font.Height;
        }


        private void GetBitmaps()
        {

            if (myBitmap == null
             || myAlphaBitmap == null
             || myBitmap.Width != Width
             || myBitmap.Height != Height
             || myAlphaBitmap.Width != Width
             || myAlphaBitmap.Height != Height)
            {
                myBitmap = null;
                myAlphaBitmap = null;
            }



            if (myBitmap == null)
            {
                myBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);//(Width,Height);
                myUpToDate = false;
            }


            if (!myUpToDate)
            {
                //Capture the TextBox control window

                this.SetStyle(ControlStyles.UserPaint, false);

                win32.CaptureWindow(this, ref myBitmap);

                this.SetStyle(ControlStyles.UserPaint, true);
                this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
                this.BackColor = Color.FromArgb(myBackAlpha, myBackColor);

            }
            //--



            Rectangle r2 = new Rectangle(0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height);
            ImageAttributes tempImageAttr = new ImageAttributes();


            //Found the color map code in the MS Help

            ColorMap[] tempColorMap = new ColorMap[1];
            tempColorMap[0] = new ColorMap();
            tempColorMap[0].OldColor = Color.FromArgb(255, myBackColor);
            tempColorMap[0].NewColor = Color.FromArgb(myBackAlpha, myBackColor);

            tempImageAttr.SetRemapTable(tempColorMap);

            if (myAlphaBitmap != null)
                myAlphaBitmap.Dispose();


            myAlphaBitmap = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);//(Width,Height);

            Graphics tempGraphics1 = Graphics.FromImage(myAlphaBitmap);

            tempGraphics1.DrawImage(myBitmap, r2, 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height, GraphicsUnit.Pixel, tempImageAttr);

            tempGraphics1.Dispose();

            //----

            if (this.Focused && (this.SelectionLength == 0))
            {
                Graphics tempGraphics2 = Graphics.FromImage(myAlphaBitmap);
                if (myCaretState)
                {
                    //Draw the caret
                    Point caret = this.findCaret();
                    Pen p = new Pen(this.ForeColor, 3);
                    tempGraphics2.DrawLine(p, caret.X, caret.Y + 0, caret.X, caret.Y + myFontHeight);
                    tempGraphics2.Dispose();
                }

            }



        }



        private Point findCaret()
        {
            /*  Find the caret translated from code at 
             * http://www.vb-helper.com/howto_track_textbox_caret.html
             * 
             * and 
             * 
             * http://www.microbion.co.uk/developers/csharp/textpos2.htm
             * 
             * Changed to EM_POSFROMCHAR
             * 
             * This code still needs to be cleaned up and debugged
             * */

            Point pointCaret = new Point(0);
            int i_char_loc = this.SelectionStart;
            IntPtr pi_char_loc = new IntPtr(i_char_loc);

            int i_point = win32.SendMessage(this.Handle, win32.EM_POSFROMCHAR, pi_char_loc, IntPtr.Zero);
            pointCaret = new Point(i_point);

            if (i_char_loc == 0)
            {
                pointCaret = new Point(0);
            }
            else if (i_char_loc >= this.Text.Length)
            {
                pi_char_loc = new IntPtr(i_char_loc - 1);
                i_point = win32.SendMessage(this.Handle, win32.EM_POSFROMCHAR, pi_char_loc, IntPtr.Zero);
                pointCaret = new Point(i_point);

                Graphics g = this.CreateGraphics();
                String t1 = this.Text.Substring(this.Text.Length - 1, 1) + "X";
                SizeF sizet1 = g.MeasureString(t1, this.Font);
                SizeF sizex = g.MeasureString("X", this.Font);
                g.Dispose();
                int xoffset = (int)(sizet1.Width - sizex.Width);
                pointCaret.X = pointCaret.X + xoffset;

                if (i_char_loc == this.Text.Length)
                {
                    String slast = this.Text.Substring(Text.Length - 1, 1);
                    if (slast == "\n")
                    {
                        pointCaret.X = 1;
                        pointCaret.Y = pointCaret.Y + myFontHeight;
                    }
                }

            }



            return pointCaret;
        }


        private void myTimer1_Tick(object sender, EventArgs e)
        {
            //Timer used to turn caret on and off for focused control

            myCaretState = !myCaretState;
            myCaretUpToDate = false;
            this.Invalidate();
        }


        private class uPictureBox : PictureBox
        {
            public uPictureBox()
            {
                this.SetStyle(ControlStyles.Selectable, false);
                this.SetStyle(ControlStyles.UserPaint, true);
                this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
                this.SetStyle(ControlStyles.DoubleBuffer, true);

                this.Cursor = null;
                this.Enabled = true;
                this.SizeMode = PictureBoxSizeMode.Normal;

            }




            //uPictureBox
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == win32.WM_LBUTTONDOWN
                 || m.Msg == win32.WM_RBUTTONDOWN
                 || m.Msg == win32.WM_LBUTTONDBLCLK
                 || m.Msg == win32.WM_MOUSELEAVE
                 || m.Msg == win32.WM_MOUSEMOVE)
                {
                    //Send the above messages back to the parent control
                    win32.PostMessage(this.Parent.Handle, (uint)m.Msg, m.WParam, m.LParam);
                }

                else if (m.Msg == win32.WM_LBUTTONUP)
                {
                    //??  for selects and such
                    this.Parent.Invalidate();
                }


                base.WndProc(ref m);
            }


        }   // End uPictureBox Class


        #endregion  // end private functions and classes


        #region Component Designer generated code
        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        #endregion


        #region New Public Properties

        [
        Category("Appearance"),
        Description("The alpha value used to blend the control's background. Valid values are 0 through 255."),
        Browsable(true),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)

        ]
        public int BackAlpha
        {
            get { return myBackAlpha; }
            set
            {
                int v = value;
                if (v > 255)
                    v = 255;
                myBackAlpha = v;
                myUpToDate = false;
                Invalidate();
            }
        }

        #endregion



    }  // End AlphaTextBox Class


}  // End namespace ZBobb


//----
View Code

用户自定义控件(含源代码)-圆角Panel

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace myControlLibrary
{
    public partial class RoundPanel : System.Windows.Forms.Panel
    {
        public RoundPanel()
        {
            InitializeComponent();

            this.Padding = new System.Windows.Forms.Padding(0, 0, 0, 0);
            this.Margin = new System.Windows.Forms.Padding(0, 0, 0, 0);
            this.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
        }


        // 圆角
        // ===============================================================================================
        private int _Radius;  // 圆角弧度

        /// <summary>圆角弧度(0为不要圆角)</summary>
        [Browsable(true)]
        [Description("圆角弧度(0为不要圆角)")]
        public int _setRoundRadius
        {
            get
            {
                return _Radius;
            }
            set
            {
                if (value < 0) { _Radius = 0; }
                else { _Radius = value; }
                base.Refresh();
            }
        }


        // 圆角代码
        public void Round(System.Drawing.Region region)
        {
            // -----------------------------------------------------------------------------------------------
            // 已经是.net提供给我们的最容易的改窗体的属性了(以前要自己调API)
            System.Drawing.Drawing2D.GraphicsPath oPath = new System.Drawing.Drawing2D.GraphicsPath();
            int x = 0;
            int y = 0;
            int thisWidth = this.Width;
            int thisHeight = this.Height;
            int angle = _Radius;
            if (angle > 0)
            {
                System.Drawing.Graphics g = CreateGraphics();
                oPath.AddArc(x, y, angle, angle, 180, 90);                                 // 左上角
                oPath.AddArc(thisWidth - angle, y, angle, angle, 270, 90);                 // 右上角
                oPath.AddArc(thisWidth - angle, thisHeight - angle, angle, angle, 0, 90);  // 右下角
                oPath.AddArc(x, thisHeight - angle, angle, angle, 90, 90);                 // 左下角
                oPath.CloseAllFigures();
                Region = new System.Drawing.Region(oPath);
            }
            // -----------------------------------------------------------------------------------------------
            else
            {
                oPath.AddLine(x + angle, y, thisWidth - angle, y);                         // 顶端
                oPath.AddLine(thisWidth, y + angle, thisWidth, thisHeight - angle);        // 右边
                oPath.AddLine(thisWidth - angle, thisHeight, x + angle, thisHeight);       // 底边
                oPath.AddLine(x, y + angle, x, thisHeight - angle);                        // 左边
                oPath.CloseAllFigures();
                Region = new System.Drawing.Region(oPath);
            }
        }
        // ===============================================================================================


        public RoundPanel(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs pe)
        {
            base.OnPaint(pe);
            Round(this.Region);  // 圆角
        }

        protected override void OnResize(EventArgs eventargs)
        {
            base.OnResize(eventargs);
            base.Refresh();
        }
    }
}
View Code

[WPF]WPF4.0中的字体呈现改进

[WPF]WPF4.0中的字体呈现改进
http://www.21tx.com 2009年11月01日 博客园 紫色永恒
  注意TextOptions.TextFormattingMode这个附加属性,它提供了两种设置:
  · Ideal – 就是WPF出现以后一直使用的方式。
  · Display – Display模式,今天的主角。
  他们都是依赖属性,所以其设定的值可以被子元素继承。
  一个小例子 :
    <StackPanel> 
      <TextBlock> 
        Hello World ... Ideal text formatting 
      </TextBlock> 
      <TextBlock TextOptions.TextFormattingMode="Display"> 
        Hello World ... Display text formatting 
      </TextBlock> 
    </StackPanel>

  爽了吧。
  再做个对比:

  更重要的是,WPF4.0中同时针对东亚字体的渲染进行了优化。是的,现在它支持点阵字体了。
  对比下:


  而开启这个新特性十分简单,只需为FontFamily设置一种点阵字体就可以了。比如FontFamily="SimSun"就是使用宋体。
  文章来源:http://024hi.cnblogs.com/
 
View Code

Socket实现多人同时聊天

 

          

     项目结构图                                         服务端程序
 

      

                                                            客户端效果
 
 
 
下面这个实例是一个完整的使用Socket实现的聊天(只限于局域网,如果能提供一个高权限的IP就可以实现类似QQ聊天),其中的原理是:首先开启服务端,打开侦听(任何端口为6600的IP),下面实现的代码:服务端+客户端【VS2005 C#.NET 2.0】
 
【服务端】三个窗体:About.cs,ServerMain.cs,Set.cs
ServerMain.cs窗体代码
 

using System;
using System.Text;
using System.Windows.Forms;

using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Xml;

namespace Server
{
publicpartialclass ServerMain : Form
{
public ServerMain()
{
InitializeComponent();
}

privatevoid ServerMain_Load(object sender, EventArgs e)
{
this.CmdStar.Enabled =true;
this.CmdStop.Enabled =false;
}

privatevoid 配置参数ToolStripMenuItem_Click(object sender, EventArgs e)
{
Set TSet =new Set();
TSet.ShowDialog();
}

privatevoid 关于ToolStripMenuItem_Click(object sender, EventArgs e)
{
About TAbout =new About();
TAbout.Show();
}
///<summary>
/// 获得XML文件中的端口号
///</summary>
///<returns></returns>
privateint GetPort()
{
try
{
XmlDocument TDoc =new XmlDocument();
TDoc.Load("Settings.xml");
string TPort = TDoc.GetElementsByTagName("ServerPort")[0].InnerXml;
return Convert.ToInt32(TPort);

}
catch { return6600; }//默认是6600
}

//声明将要用到的类
private IPEndPoint ServerInfo;//存放服务器的IP和端口信息
private Socket ServerSocket;//服务端运行的SOCKET
private Thread ServerThread;//服务端运行的线程
private Socket[] ClientSocket;//为客户端建立的SOCKET连接
privateint ClientNumb;//存放客户端数量
privatebyte[] MsgBuffer;//存放消息数据

privatevoid CmdStar_Click(object sender, EventArgs e)
{
ServerSocket =new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//提供一个 IP 地址,指示服务器应侦听所有网络接口上的客户端活动
IPAddress ip = IPAddress.Any;
ServerInfo=new IPEndPoint(ip,this.GetPort());
ServerSocket.Bind(ServerInfo);//将SOCKET接口和IP端口绑定
ServerSocket.Listen(10);//开始监听,并且挂起数为10

ClientSocket =new Socket[65535];//为客户端提供连接个数
MsgBuffer =newbyte[65535];//消息数据大小
ClientNumb =0;//数量从0开始统计

ServerThread =new Thread(new ThreadStart(RecieveAccept));//将接受客户端连接的方法委托给线程
ServerThread.Start();//线程开始运行

CheckForIllegalCrossThreadCalls =false;//不捕获对错误线程的调用

this.CmdStar.Enabled =false;
this.CmdStop.Enabled =true;
this.StateMsg.Text ="服务正在运行..."+" 运行端口:"+this.GetPort().ToString();
this.ClientList.Items.Add("服务于 "+ DateTime.Now.ToString() +" 开始运行.");
}

//接受客户端连接的方法
privatevoid RecieveAccept()
{
while (true)
{
//Accept 以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket。
//在阻止模式中,Accept 将一直处于阻止状态,直到传入的连接尝试排入队列。连接被接受后,原来的 Socket 继续将传入的连接请求排入队列,直到您关闭它。
ClientSocket[ClientNumb] = ServerSocket.Accept();
ClientSocket[ClientNumb].BeginReceive(MsgBuffer, 0, MsgBuffer.Length, SocketFlags.None,
new AsyncCallback(RecieveCallBack),ClientSocket[ClientNumb]);
lock (this.ClientList)
{
this.ClientList.Items.Add(ClientSocket[ClientNumb].RemoteEndPoint.ToString() +" 成功连接服务器.");
}
ClientNumb++;
}
}

//回发数据给客户端
privatevoid RecieveCallBack(IAsyncResult AR)
{
try
{
Socket RSocket = (Socket)AR.AsyncState;
int REnd = RSocket.EndReceive(AR);
//对每一个侦听的客户端端口信息进行接收和回发
for (int i =0; i < ClientNumb; i++)
{
if (ClientSocket[i].Connected)
{
//回发数据到客户端
ClientSocket[i].Send(MsgBuffer, 0, REnd,SocketFlags.None);
}
//同时接收客户端回发的数据,用于回发
RSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(RecieveCallBack), RSocket); 
}
}
catch { }

}

privatevoid CmdStop_Click(object sender, EventArgs e)
{
ServerThread.Abort();//线程终止
ServerSocket.Close();//关闭socket

this.CmdStar.Enabled =true;
this.CmdStop.Enabled =false;
this.StateMsg.Text ="等待运行...";
this.ClientList.Items.Add("服务于 "+ DateTime.Now.ToString() +" 停止运行.");
}

privatevoid ServerMain_FormClosed(object sender, FormClosedEventArgs e)
{
ServerThread.Abort();//线程终止
ServerSocket.Close();//关闭SOCKET
Application.Exit();
}
}
}

 
 
 
Set.cs代码
 

using System;
using System.Text;
using System.Windows.Forms;

using System.Xml;

namespace Server
{
publicpartialclass Set : Form
{
public Set()
{
InitializeComponent();
}

privatevoid Set_Load(object sender, EventArgs e)
{
this.GetPort();
}

privatevoid GetPort()
{
try
{
XmlDocument TDoc =new XmlDocument();
TDoc.Load("Settings.xml");
string TPort = TDoc.GetElementsByTagName("ServerPort")[0].InnerXml;
this.Port.Text = TPort;
}
catch { }
}

privatevoid CmdSave_Click(object sender, EventArgs e)
{
try
{
XmlDocument TDoc =new XmlDocument();
TDoc.Load("Settings.xml");

XmlElement Root = TDoc.DocumentElement;
XmlElement newElem = TDoc.CreateElement("ServerPort");
newElem.InnerXml =this.Port.Text;

Root.ReplaceChild(newElem, Root.LastChild);

TDoc.Save("Settings.xml");

MessageBox.Show("参数保存成功!");
this.Close();
}
catch
{
MessageBox.Show("参数写入XML文件不成功!");
}
}
}
}

 
 
附:
Settings.xml
 
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ServerPort>6600</ServerPort>
 
 
 
 
 
******************************************************************************************************
 
 
【客户端代码】ClientMain.cs窗体
 

using System;
using System.Text;
using System.Windows.Forms;

using System.Net;
using System.Net.Sockets;

namespace Client
{
publicpartialclass ClientMain : Form
{
public ClientMain()
{
InitializeComponent();
}

private IPEndPoint ServerInfo;
private Socket ClientSocket;
//信息接收缓存
private Byte[] MsgBuffer;
//信息发送存储
private Byte[] MsgSend;

privatevoid ClientMain_Load(object sender, EventArgs e)
{
this.CmdSend.Enabled =false;
this.CmdExit.Enabled =false;
//定义一个IPV4,TCP模式的Socket
ClientSocket =new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
MsgBuffer =new Byte[65535];
MsgSend =new Byte[65535];
//允许子线程刷新数据
CheckForIllegalCrossThreadCalls =false;
this.UserName.Text =Environment.MachineName;
}

privatevoid CmdEnter_Click(object sender, EventArgs e)
{
//服务端IP和端口信息设定,这里的IP可以是127.0.0.1,可以是本机局域网IP,也可以是本机网络IP
ServerInfo =new IPEndPoint(IPAddress.Parse(this.ServerIP.Text), Convert.ToInt32(this.ServerPort.Text));

try
{
//客户端连接服务端指定IP端口,Sockket
ClientSocket.Connect(ServerInfo);
//将用户登录信息发送至服务器,由此可以让其他客户端获知
ClientSocket.Send(Encoding.Unicode.GetBytes("用户: "+this.UserName.Text +" 进入系统!\n"));
//开始从连接的Socket异步读取数据。接收来自服务器,其他客户端转发来的信息
//AsyncCallback引用在异步操作完成时调用的回调方法
ClientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), null);

this.SysMsg.Text +="登录服务器成功!\n";
this.CmdSend.Enabled =true;
this.CmdEnter.Enabled =false;
this.CmdExit.Enabled =true;
}
catch
{
MessageBox.Show("登录服务器失败,请确认服务器是否正常工作!");
}
}

privatevoid ReceiveCallBack(IAsyncResult AR)
{
try
{
//结束挂起的异步读取,返回接收到的字节数。 AR,它存储此异步操作的状态信息以及所有用户定义数据
int REnd = ClientSocket.EndReceive(AR);

lock (this.RecieveMsg)
{
this.RecieveMsg.AppendText(Encoding.Unicode.GetString(MsgBuffer, 0, REnd));
}
ClientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);

}
catch
{
MessageBox.Show("已经与服务器断开连接!");
this.Close();
}

}
//点击发送之后没有直接添加到信息列表中,而是传到服务器,由服务器转发给每个客户端
privatevoid CmdSend_Click(object sender, EventArgs e)
{

MsgSend = Encoding.Unicode.GetBytes(this.UserName.Text +"说:\n"+this.SendMsg.Text +"\n");
if (ClientSocket.Connected)
{
//将数据发送到连接的 System.Net.Sockets.Socket。
ClientSocket.Send(MsgSend);
this.SendMsg.Text ="";

}
else
{
MessageBox.Show("当前与服务器断开连接,无法发送信息!");
}
}

privatevoid CmdExit_Click(object sender, EventArgs e)
{
if (ClientSocket.Connected)
{
ClientSocket.Send(Encoding.Unicode.GetBytes(this.UserName.Text +"离开了房间!\n"));
//禁用发送和接受
ClientSocket.Shutdown(SocketShutdown.Both);
//关闭套接字,不允许重用
ClientSocket.Disconnect(false);
}
ClientSocket.Close();

this.CmdSend.Enabled =false;
this.CmdEnter.Enabled =true;
this.CmdExit.Enabled =false;
}

privatevoid RecieveMsg_TextChanged(object sender, EventArgs e)
{
this.RecieveMsg.ScrollToCaret();
}

privatevoid SendMsg_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyValue ==13)
{
e.Handled =true;
this.CmdSend_Click(this, null); 
}
}
}
}

个性签名:做要做好,做到不三不四不如不做。
 
View Code

WPF入门指南教程

http://tech.it168.com/zt/wpf/index.html
WPF入门指南一:快速入门
【IT168技术文档】
  摘要:WPF是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分。本文旨在通过一些简单的示例,让你对WPF有一个概要的认识。

主要内容
1.概述
2.WPF的组成
3.WPF的编程架构
4.XAML
5.WPF中的控件集

一.概述
WPF (Windows Presentation Foundation)是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了分离界面设计人员与开发人员的工作;同时它提供了全新的多媒体交互用户图形界面。

二.WPF的组成
WPF的构成组件如下所示,其中PresentationFramework、PresentationCore、milcore三部分是WPF的核心组件: 
 

三.WPF的编程架构 
   


  WPF整体的编程架构如图2所示,可以看到,它其中包含了文档服务、用户界面服务、多媒体服务以及一些其它的基本服务。在用户界面服务中,提供了应用程序 服务、部署服务、控件集、布局以及数据绑定等;在多媒体服务中可以看到WPF几乎可以处理所有的媒体类型,包括图片、音频、视频、动画、2D、3D、文本 等。


四.XAML
在WPF中提供了两种API,一种是用于普通编程的API,比如我们可以用C#、VB.NET等 语言进行编程,另一种是基于XML的API,称为XAML(Extensible Application Markup Language),引入XAML使得UI代码和应用程序逻辑代码完全分离,它是一种标记语言,支持声明式编程,由于XAML是基于XML的,所以它拥有 XML的所有规则和定义,非常容易进行扩展。看下面两段简单的代码:
XAML代码:

<Button Name="bt1" Background="Blue" Foreground="Red">

  This is a button

</Button>

C#代码:

Button btn1 = new Button();

btn1.Content = "This is a button";

btn1.Background = Brushes.Blue;

btn1.Foreground = Brushes.Red;

 
五.WPF控件集
  在WPF中,提供了非常丰富的控件集,如表一所示:
分类                                          控件
Editing                                     CheckBox, ComboBox, PasswordBox, RadioButton, RichTextBox, Slider, TextBox
List                                         Selection ListBox, ListView, TreeView
User Information                 Label, ProgressBar, Popup, ToolTip
Action                        Button, ContextMenu, Menu, Separator, StatusBar, Thumb, ToolBar
Appearance             Border, BulletDecorator, Decorator, Image, Viewbox
Dialog boxes           OpenFileDialog, PrintDialog, SaveFileDialog
Containers               Expander, GroupBox, RepeatButton, ScrollBar, ScrollViewer, TabControl
Layout                       Canvas, DockPanel, Grid, GridSplitter, Panel, StackPanel, VirtualizingStackPanel, WrapPanel
                                  Navigation Frame, Hyperlink
Documents       DocumentViewer, FlowDocumentPageViewer, FlowDocumentReader, FlowDocumentScrollViewer

以上只是对WPF作一个概要的介绍,在后面的文章中,我会详细的去讲WPF中一些控件的用法\数据绑定、资源处理、样式与模版、文档编程、多媒体编程等内容。
WPF开发简介
【IT168技术文档】
    要赢得世界,必须在恰当的时间做出恰当的事。这一点,微软做到了。历经微软DOS,Win 31, Win95, Win 98, Win 2k, WinXP, 一路走来,一次比一次热闹,一个比一个精彩、炫丽。Vista的宣传更是轰轰烈烈,喧嚣日上。Microsoft总是有能力在恰当的时间制造出意料中的轰 动,Vista一经推出便让IT业界趋之若鹜。

    一、掀起你的盖头来,让我看看你的脸——.NET Framework 3.0

    与微软对Vista锣鼓宣天的推广相比,对与之相伴的.NET Framework 3.0却显得非常低调。微软没将它作为宣传重点。所有的幕后英雄总是默默无闻。对普通用户而言,他们不关心舞台背后的一切,它们更关心的是前台的表现,是 那种炫丽奢华的感觉。真正热捧幕后英雄的,也是我们这群在幕后默默耕耘的程序员们。我们对.NET Framework 3.0有种自然而然的亲切感,因为我们将在此架构上为Vista编写各种程序。

    作为微软精心打造的新一代.NET架构,.NET Framework 3.0在Vista时代担当最主要的应用程序接口。.NET曾被微软寄予厚望和重托,比尔·盖茨先生欲用之来实现一统江湖之志。事实证明,微软对.Net 的推出是成功。越来越多的应用程序开发者已从Win32和JAVA架构转向.NET开发,越来越多的网站开发人员爱上了asp.net。这让微软看到了希 望,他们踌躇满志,他们加紧了对.NET的研发,升级速度越来越快,.NET Framework 3.0就是微软在.NET Framework 2.0后最重要的升级。甚至有人惊呼,我们象刘翔一样地飞奔也跟不上微软的速度!

    到了.NET Framework 3.0,.NET令程序员更称心、并逐渐“倾心”。在众多的开发环境,微软的开发系列产品,让它在与其它架构的较量中逐渐抢得上风而独在鳌头。

    Vista集成.NET Framework 3.0,可谓珠联璧合。可以这么讲,没有.Net Framework 3.0这个幕后英雄,Vista会黯淡很多。

    .NET Framework 3.0(原代号“WinFX”),它构建于.NET Framework 2.0之上,同时新增了四个大组件:Windows Presentation Foundation(WPF),Windows Workflow Foundation(WWF)、Windows Communication Foundation(WCF)和Windows CardSpace,而这些恰是NET Framework 3.0最具价值的部分。

    其中,最令人瞩目的是WPF(原来代号为“Avalon”),它是微软全新的图形界面引擎,为各种应用提供统一的界面技术。程序员在WPF的帮助下,要开 发出媲美Mac程序的酷炫界面已不再是遥不可及的奢望。Windows Workflow Foundation用于开发基于工作流的应用程序;Windows Communication Foundation则用于开发面向服务的应用程序,它是新一代通讯和网络开发框架,为不同类型的网络应用程序提供了一个通用的编程模型,尤其是包含其中 的PeerChannel模块,以难以置信的方式消除了开发P2P应用程序的复杂性和困难度;而Windows CardSpace提供数字标志用户控件。如果把这些林林总总小巧精美的产品比做是珍珠的话,那么,需要一根金线将这些珍珠串起来,珍珠才更有价值,珍珠 才熠熠生辉。而串起这颗珍珠的金线,就是.NET Framework 3.0。

    二、搭建Vista程序的开发环境

    1. 如果你使用的是Vista操作系统,那么,由于Vista已集成了.NET Framework 3.0,所以不必再单独安装.NET Framework 3.0。但是,如果你是Windows XP或Windows 2003下开发,则必须安装.NET Framework 3.02. 要开发Vista应用程序,则必须有Vista SDK开发包的支持。Windows SDK for Windows Vista and .NET Framework 3.0包含了大量库文件、头文件、文档、示例和工具。由于Vista操作系统的推出时间比Visual Studio 2005开发工具要晚,因此,针对Windows Vista开发的Windows SDK和.NET Framework 3.0并未集成到Visual Studio 2005中。因此,必须下载此SDK,个头不小(当前最新版本是1.15GB),下载和安装时需要你足够的耐心和毅力,相信这个对于我们这些一向好脾气的 程序员产不在话下。

    3. 安装Visual Studio 2005(中/英文版均可,建议你习惯使用英文版,因为所有版本都是英文版的首先投放市场,而英文版到中文版,现实有三个月以上的时间差,而对于我们这些 母语非英语的开发人员,英语关是一个必须跨越的槛,缘引鲁迅老先生的名言改编一下:世界并没有自己熟悉的路,走得多了,也就熟了路!英文看得多了,自然也 就熟悉了)。

    4. 下载并安装Visual Studio 2005 SP1,Visual Studio 2005 SP1 Update for Windows Vista。Visual Studio 2005 SP1修补Visual Studio 2005许多错误,并增强了一些性能,由于该补丁包要对Visual Studio 2005和Vista进行详细检测,安装比较耗时。

    5.为了方便程序员开发.NET Framework 3.0下的新型应用,微软还推出了VS 2005 Extensions for .NET Framework 3.0(Workflow)和VS 2005 Extensions for .NET Framework 3.0(WCF&WPF)扩展组件,为Visual Studio 2005提供开发Windows Workflow Foundation、Windows Communication Foundation和Windows Presentation Foundation类型应用程序的模版。

    6. 如果要学WPF,特别建议你安装Expression Blend(目前版本是Expression Blend 2 Agust Preview版)。这样,你可以在vs2005新建一个WPF项目,然后用Blend打开此项目,在Blend中对它进行界面设计和美化等工作,然后再 转到vs2005下进行程序编码(两个程序之间支持来回切换)。
三、WPF简介

    Windows Presentation Foundation(WPF)是微软新一代图形系统,运行在.NET Framework 3.0架构下,为用户界面、2D/3D 图形、文档和媒体提供了统一的描述和操作方法。基于DirectX 9/10技术的WPF不仅带来了前所未有的3D界面,而且其图形向量渲染引擎也大大改进了传统的2D界面,比如Vista中的半透明效果的窗体等都得益于 WPF。微软还提供了专门的界面开发语言XAML(eXtensible Application Markup Language,可扩展应用程序标记语言),使得界面描述代码和程序代码得以分开,从而提高了开发效率并有利于团队开发。

    XAML是微软为构建下一代应用程序界面而创建的一种新的基于XML的描述性语言,它可对WPF程序的所有界面元素进行定制,从而构成具有WPF风格的界 面,并最终形成一个组织良好的XML文档。由于它最新会被编译成.Net后台代码,因此它能够同后台进行逻辑处理的.NET语言如C#、J#、C++、 VB等协同工作,其工作性质类似于ASP.NET中的HTML。同HTML一样,XAML既可以直接编码,也可以由专门的工具生成。目前最好的XAML编 辑器就是上面我建议你安装的Expression Blend了,但你也可以使用Windows SDK for Windows Vista中的XAMLPad,但功能却显得非常简单。

    实际上,大多数WPF程序将同时包含XAML代码和程序代码,首先使用XAML定义程序界面,然后再用.NET语言编写相应的逻辑代码。跟ASP.NET 类似,逻辑代码既可以直接嵌入XAML文件中,也可以将它保存为独立的代码文件。尽管XAML并非设计WPF程序所必须,按照传统方式使用程序代码来实现 界面依然有效,但是如果使用XAML,界面设计和逻辑设计可以完全分离,不但使程序的开发和维护更加方便,而且在团队开发中,可以使程序员专注于业务逻辑 的实现,而将界面设计交由专业人员来完成,从而使各类人员在项目中各尽其能各展其长,开发出功能强大、界面一流的WPF程序。
 
WPF指南之XAML概述
 
【IT168 技术文档】
    在我开始看WPF文档开始的几天里,脑子里形成了一种错误的想法:WPF不就是XAML码?当时的感觉就是郁闷啦,我学习WPF还得弄这个东西。给人的第一感觉就是WPF很复杂。虽然对WPF的熟悉和了解还不是特别多,但现在已经知道这确实是一种错误的想法。

    Charles Petzold先生曾有一篇文章介绍了WPF、XAML的一些关系(The Two APIs)。文章中说明了WPF为什么很复杂:因为WPF有两套API,一套用于普通的编码访问(比如C#、VB.NET等其中.NET支持的语言。而另 外一套就是基于XML的API,被称为XAML(Extensible Application Markup Language)。

    XAML实现UI代码和应用程序逻辑代码的分离。在.NET 3.0和Windows Vista中,XAML与WPF一起建立整个的UI。由于XAML是基于XML的,所以每个XAML代码都肯定是一个完整的XML文件。XAML继承了 XML所有的定义和规则。XAML与其他XML扩展不同之处就是他所表示的意义。每个XAML元素是一个.NET CLR类。基于XML使得我们非常容易扩展和操作XAML。利用XAML的WPF这种关系,开发人员可以单独的设计漂亮的UI,也许真正的美工会更多的出 现。我们可以把程序逻辑写在单独的文件或者是内联嵌入到XML文件。

    在XAML中使用得最多的XML功能应该有三个:命名空间、属性和子元素。

    先看一个简单的XAML的例子:

<Window x:Class="FirstXAML.Window1"

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

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

Title="FirstXAML" Height="200" Width="300"

>

<Canvas>

</Canvas>

</Window> 

 
    其中的xmlns就是XML中的名字空间,在W3C中xmlns是如下定义的:

    XML namespaces provide a simple method for qualifying element and attribute names used in Extensible Markup Language documents by associating them with namespaces identified by URI references.

    简单地说就是xmlns提供了一种方法把URI引用的名字空间定义为当前XML文件的元素和属性的默认命名空间。这里表示当前这个XML文档,也就是我们的XAML文件,它的默认的命名空间就是http://schemas.microsoft.com/winfx/2006/xaml/presentation。

    然后是属性和子元素,XML对属性的表示除了可以用Property外,还可以用子元素,在XAML中也是如此,看一个简单的例子:

<Button Width="6">

<Button.Background>White</Button.Background>

</Button> 
 
     例子当中就使用了属性和子元素两种方式来指定属性。其中的Width是直接用属性表示,Background属性是用子元素表示。在多数时候,但不是所有,你可以自由选择这两种表示方式之一。

    XAML被编译为BAML(Binary Application Markup Language)文件。通常,BAML文件比XAML更小,编译后的BAML都是Pre-tokenized的,这样在运行时能更快速的加载、分析 XAML等等。这些BAML文件被以资源的形式嵌入到Assembly当中。同时生成相应的代码(文件名称是**.g.cs或者**.g.vb),这些代 码根据XAML元素分别生成命名的 Attribute字段。以及加载BAML的构造函数。

    最后,关于XAML的优点,我附上一点翻译过来的条款,可能更直观。

    XAML除了有标记语言、XML的优点外,还有如下一些优点:

    用XAML设计UI更简单
    XAML比其他的UI设计技术所需编码更少。
    XAML设计的UI方便转移、方便在其他环境提交。比如在Web或Windows Client。
    用XAML设计动态UI非常容易
    XAML给UI设计人员带来新的革命,现在所有的设计人员不再需要.NET开发的知识同样可以设计UI。在不远的将来,终端用户可以看到更漂亮的UI。
WPF指南之WPF的结构
【IT168技术文档】
    WPF进入我们的生活已经有一段时间。个人认为在UI的实践中,用户需要的是易于操作的,更加绚丽的界面。这两个应该是最基本、也是最重要的宗旨。而对于 开发人员就是要用最简单的方法开发出尽可能漂亮的界面,并且效率也不能太差。除了在一些Web开发和特殊的应用中,很少有开发组配备单独的美工,至少目前 是这样吧!根据自己目前对WPF的了解程度,感觉WPF在其中某些方面确实有超强的震撼力。

    客观上讲,Vista操作系统确实给我们带来了无可比拟的视觉效果。我自己深有体会,在近2个月的时间里每天都是在Vista下的开发,回家后看到XP系统,始终有些不爽的感觉。

    WPF可以认为是MS利用原有.NET框架的一些特色,加上DirextX的产物。从下图的WPF组件中,我们可以看出最底层仍然是一些内核API。(以下两张图片都来自互联网。)


    其中红色显示的组件是WPF的核心。Milcore是一个和DirectX交互的非托管组件,非托管代码能带给我们更高效的处理,能更好的和 DirextX交互。WPF的所有显示都是由Dirext完成的。milcore中一个非常重要的功能就是Composition引擎,这个引擎对效率的 要求很高,它的具体作用稍后介绍。所以milcore放弃了一些CLR的特征来换取效率。而另外两个红色的组件都是建立在CLR基础之上,利用了.NET 的优势。

    至于其中的User32组件有什么作用,偶目前的知道的就是在WPF的某些应用场景中为了某些兼容需要使用User32,其中就有DWM(桌面窗口管理)。DWM的内容又可以写上一大堆,感兴趣的朋友可以看SDK文档。

    我们除了关心WPF的基本结构外,更重要的 是WPF提供了什么功能,请看下图:




    图中的每个黄色块都是一种媒体类型。这就表示WPF可以处理几乎所有的媒体类型:位图、3D、音频、视频和文本等等。通过WPF,它集成了现在的 GDI/GDI+、D3D/OPENGL以及多媒体的DSHOW等等。所有的东西都是等同对象,不管的3D还是2D,或者文本。

    结构图中的Animate块贯串了整个的结构,因为在WPF中我们可以对所有的可视内容进行动画操作。这是非常让人期待的功能。Animate下面我们再 次看到了Composition引擎,前面提到过它是位于milcore组件中。开发过程中,我们的界面元素功能有多种,比如图片,视频等等,最后显示到 窗口的内容可以认为只是一张图片(准确说是Surface)。这个引擎的作用就是合成这些图片和视频元素最后进行提交显示。
WPF的数据处理
【IT168 技术文档】
    数据绑定,这是WPF提 供的一个真正的优点。除了可以用在传统的绑定环境中,数据绑定已经被扩展应用到控件属性上。学习应用数据绑定,也能真正的体现XAML的好处。到底什么是 数据绑定呢?也许你从字面上已经理解的很不错了。通过数据绑定,我们在应用程序UI和程序逻辑之间建立了一种联系。正常建立绑定后,在数据的值发生改变 后,绑定到数据的元素将自动更新、体现出数据的变化。
    同样,我们先看几个相关的知识点:

    1、DataContext属性。设置DataContext属性,其实就是指定数据上下文。那么数据上下文又是什么呢?又是一个新的概念:数据上下文允 许元素从它的父元素继承数据绑定的数据源。很简单,在某个元素的DataContext中指定的值,那么在这个元素的子元素也可以使用。注意,如果我们修 改了FrameworkElement或者FrameworkContentElement元素的DataContext属性,那么元素将不再继承 DataContext值。也就是说新设置的属性值将覆盖父元素的设置。如何设置DataContext属性,稍后说明。

    2、数据源的种类。也许,WPF提供的数据绑定只是实现了一项普通的功能而已,但是,WPF中所支持的多种数据源使得它的数据绑定功能将更加强大。现在,WPF支持如下四种绑定源:

    (1)、任意的CLR对象:数据源可以是CLR对象的属性、子属性以及Indexers。对于这种类型的绑定源,WPF采用两种方式来获取属性值:A)、 反射(Reflection);B)、CustomTypeDescriptor,如果对象实现了ICustomTypeDescriptor,绑定将使 用这个接口来获取属性值。
    (2)、XML结点:数据源可以是XML文件片断。也可以是XMLDataProvider提供的整个XML文件。
    (3)、ADO.NET数据表。我对ADO.NET的了解不够,在此不做过多评论。
    (4)、Dependency对象。绑定源可以是其它DependencyObject的DependencyProperty属性。

    3、数据绑定的方式:(1)、OneWay,单一方向的绑定,只有在数据源发生变化后才会更新绑定目标。(2)、TwoWay,双向绑定,绑定的两端任何 一端发生变化,都将通知另一端。(3)、OneTime,只绑定一次。绑定完成后任何一端的变化都不会通知对方。

    在上面的第二点我介绍了数据源的种类,注意这里的概念和下面要说明的指定数据源的方式的区别。目前,指定数据源有三种方式,我们可以通过任何一种方式来指定上述的任何一种数据源:

    (1)、通过Source标记。我们可以在使用Binding使用Source标记显式指定数据源。
    (2)、通过ElementName标记。这个ElementName指定了一个已知的对象名称,将使用它作为绑定数据源。
    (3)、通过RelativeRource标记。这个标记将在后面说明ControlTemplate和Style时再进行说明。

    现在我们说明了很多和数据源相关的内容。但是再绑定的时候,我们还需要指定绑定对象的属性名称。所以WPT提供了一个Path标记。它被用来指定数据源的属性。也即是数据源将在数据源对象的Path所指定的属性上寻找属性值。
  在介绍WPF数据绑定源的种类时,第一种就是任意的CLR对象。这里需要注意的是WPF虽然支持任意的CLR对象,但是一个普通的CLR对象类却不行。我们还需要在CLR对象类上实现一种变化通知机制。

    WPF把这种通知机制封装在了INotifyPropertyChanged接口当中。我们的CLR对象类只要实现了这个接口,它就具有了通知客户的能力,通常是在属性改变后通知绑定的目标。

    下面是一个简单的例子,实现了一个支持通知功能的Camera类:
using System;
using System.ComponentModel;
using System.Windows.Media.Media3D;
namespace LYLTEST
{
public class Camera : INotifyPropertyChanged
{
private PerspectiveCamera m_Camera;
public event PropertyChangedEventHandler PropertyChanged;
public Camera()
{
m_Camera = new PerspectiveCamera();
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public PerspectiveCamera CameraProp
{
get { return m_Camera; }
set
{
if (value != m_Camera)
{
this.m_Camera = value;
NotifyPropertyChanged("CameraProp");
}
}
}
}
}
 
    这一段代码很简单,首先引入类中使用的INotifyPropertyChanged和PerspectiveCamera需要的名字空间。这里与普通 CLR类的区别在于首先有一个公有的PropertyChangedEventHandler事件类型。然后我们在.NET属性包装CameraProp 判断属性是否发生了变化,如果是,则用当前是属性名称字符串“CameraProp”调用另一个私有函数NotifyPropertyChanged。由 它根据属性的名称构造一个PropertyChangedEventArgs对象,并完成对PropertyChanged的调用。它才是属性变化时真正 应该调用的一个通知事件。

    最后一点,如果我们需要通知所以的属性都发生了变化,则将上面的属性字符串“CameraProp”用参数NULL替代即可。
  前面讲过,通过实现INotifyPropertyChanged,我们可以改变使任意的CLR对象支持WPF的绑定源。但是, INotifyPropertyChanged通常只应用在单个的类属性上。在现实应用中,我们还会遇到另外一种情况:我们需要监视某一堆的数据是否发生 变化。也就是说我们绑定的数据源不再是一个单独数据对象。比如,绑定源是一个数据表时,我们希望在表中任何一条数据发生变化就能得到通知。(这里暂不考虑 WPF绑定对ADO.NET的支持。)

    WPF提供了一个ObservableCollection类,它实现了一个暴露了INotifyPropertyChanged的数据集合。也就是说我 们不需要自己对每个单独的数据实现INotifyPropertyChanged结构。我们先看看如何实现一个简单的绑定数据集合。
namespace NSLYL
{
public class LYLDataObj
{
public LYLDataObj(string name, string description)
{
this.name = name;
this.description = description;
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Description
{
get { return description; }
set { description = value; }
}
private string name;
private string description;
}
public class LYLDataObjCol : ObservableCollection<LYLDataObj>
{
public LYLDataObjCol()
{
this.Add(new LYLDataObj("Microsot", "Operating System"));
this.Add(new LYLDataObj("Google", "Search"));
}
}
}
 
代码很简单,基本上就是这样的一个模板。然后,我们就可以把LYLDataObjCol绑定到一个需要多项数据的Element之上,比如ListBox、ComboBox等等。
<ListBox ItemsSource="{StaticResource dataObj}" .../>
 
   绑定之后,只要我的LYLDataObjCol对象发送了变化,ListBox、ComboBox的数据也会有对应的变化。

    到现在,我们已经知道在绑定的时候有两种指定数据源的方式:1、DataContext,关于它我们在这个Post有简单介绍。2、直接用Binding 类的Source属性。那么,我们在使用的时候如何区别呢?首先,Source的优先级比DataContext高,只有Source不存在,或者在当前 Source到不到需要的属性时才会查找DataContext。除此之外,这两者没有真正的区别,只是建议使用Source,它能有助于我们调试应用程 序。因为通过它可以明确的得到Source的信息。而DataContext支持一种继承。可以在父Element指定Source源。这同时也成为了 DataContext的一个优点:如果多个Element需要绑定同一个Source源,那么我们只需要在一个地方指定DataContext,就可以 在其子Element使用。
WPF中的命令简述
【IT168技术文档】 
    在我们日常的应用程序操作中,经常要处理各种各样的命令和进行相关的事件处理,比如需要复制、粘贴文本框中的内容;上网查看网页时,可能需要返回上一网页 查看相应内容;而当我们播放视频和多媒体时,我们可能要调节音量,快速拖动到我们想看的片段等等。在Winform编程中,我们经常使用各种各样的控件来 解决此类问题,当然我们也必须编写一堆代码来处理各种各样的命令和事件处理。那么,Windows Presentation Foundation (WPF)作为微软新一代图形图像支援系统,它是如何处理这些命令及事件的呢?

    在WPF中,许多控件都自动集成了固有的命令集。比如文本框TextBox就提供了复制(Copy),粘贴(Paste),裁切(Cut),撤消(Undo)和重做(Redo)命令等。

   WPF提供常用应用程序所用的命令集,常用的命令集包括:ApplicationCommands, ComponentCommands, NavigationCommands, MediaCommands和EditingCommands。

ApplicationCommands(应用程序命令):
CancelPrint:取消打印
Close:关闭
ContextMenu:上下文菜单
Copy:复制
CorrectionList: Gets the value that represents the Correction List command.
Cut:剪切
Delete:删除
Find:查找
Help:帮助
New:新建
NotACommand:不是命令,被忽略
Open:打开
Paste:粘贴
Print:打印
PrintPreview:打印预览
Properties:属性
Redo:重做
Replace:取代
Save:保存
SaveAs:另存为
SelectAll:选择所有的
Stop:停止
Undo:撤消

ComponentCommands(组件命令):
ExtendSelection:后接Down/Left/Right/Up, 比如:ExtendSelectionDown(Shift+Down,Extend Selection Down),ExtendSelectionLeft等
Move:后接Down/Left/Right/Up, 如:MoveDown
MoveFocus:后接Down/Forward/Back/Up, 如:MoveFocusDown
MoveFocusPage:后接Down/Up,如:MoveFocusPageUp
MoveTo:后接End/Home/PageDown/PageUp,比如:MoveToPageDown
ScrollByLine
ScrollPage:后接Down/Left/Right/Up,比如:ScrollPageLeft
SelectTo:End/Home/PageDown/PageUp,比如:SelectToEnd

NavigationCommands(导航命令):
Browse浏览: 后接Back/Forward/Home/Stop, 比如:BrowseBack
缩放显示:DecreaseZoom, IncreaseZoom, Zoom
Favorites(收藏)
页面:FirstPage, LastPage, PreviousPage, NextPage,GoToPage
NavigateJournal
Refresh(刷新)
Search(搜索)

MediaCommands(多媒体控制命令):
Treble高音:DecreaseTreble,IncreaseTreble
Bass低音:BoostBass,DecreaseBass,IncreaseBass
Channel频道:ChannelDown,ChannelUp
MicrophoneVolume麦克风音量调节:DecreaseMicrophoneVolume,IncreaseMicrophoneVolume,MuteMicrophoneVolume
ToggleMicrophoneOnOff:麦克风开关
Volume音量: DecreaseVolume,IncreaseVolume,MuteVolume
Rewind, FastForward(回放,快进)
Track轨道:PreviousTrack,NextTrack [上一段(节)]
Play,Pause,Stop,Record(播放,暂停,停止,录制)
TogglePlayPause
Select选择

EditingCommands(编辑/排版类命令):
Align对齐:AlignCenter,AlignJustify,AlignLeft,AlignRight(居中,撑满,左对齐,右对齐)
Backspace退格
TabForward,TabBackward(Tab前缩,Tab向后)
FontSize字体大小:DecreaseFontSize,IncreaseFontSize
Indentation缩排:DecreaseIndentation, IncreaseIndentation
Delete删除: Delete选中部分,DeleteNextWord:删除后一字,DeletePreviousWord:删除前一字
EnterLineBreak:换行
EnterParagraphBreak:换段
CorrectSpellingError/IgnoreSpellingError:纠正/忽略拼写错误
MoveUpByLine,MoveDownByLine: 上/下移一行,
MoveUpByPage,MoveDownByPage: 上/下移一页
MoveUpByParagraph,MoveDownByParagraph: 上/下移一段
MoveLeftByCharacter/MoveRightByCharacter:左/右移一字符
MoveLeftByWord/MoveRightByWord 左/右移一词
MoveToDocumentStart/MoveToDocumentEnd:到文章开头/结尾
MoveToLineStart/MoveToLineEnd:到一行的开头/结尾
SelectUpByLine,SelectDownByLine:向上/下选一行
SelectUpByPage,SelectDownByPage:向上/下选一页
SelectUpByParagraph,SelectDownByParagraph:向上/下选一段
SelectLeftByCharacter,SelectRightByCharacter:向左/右选中一字
SelectLeftByWord,SelectRightByWord:向左/右选中一词
SelectToDocumentStart,SelectToDocumentEnd: 选中到篇头/篇尾
SelectToLineStart/SelectToLineEnd:选中到行首/行尾
ToggleBold, ToggleItalic, ToggleUnderline(加粗,斜体,下划线)
ToggleBullets, ToggleNumbering(列表:加点,加数字)
ToggleInsert:插入
ToggleSuperscript,ToggleSubscript(上标字,下标字)
先来举一个简单的例子:

XAML代码:

<StackPanel>
<Menu>
<MenuItem Command="ApplicationCommands.Paste" />
</Menu>
<TextBox />
</StackPanel>
 
C#代码:
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);
pasteMenuItem.Command = ApplicationCommands.Paste;
 
上面代码演示了将对文本框设置为焦点时,菜单项可用,点击菜单项时,将执行粘贴命令。


下面列出关于Command的四个概念和四个小问题:
1、WPF中Command(命令)的四个概念:
(1)命令command:要执行的动作。
(2)命令源command source:发出命令的对象(继承自ICommandSource)。
(3)命令目标command target:执行命令的主体
(4)命令绑定command binding:映射命令逻辑的对象
比 如在上面示例中,粘贴(Paste)就是命令(command), 菜单项(MenuItem)是命令源(command source), 文本框(TextBox)是命令目标对象(command target), 命令绑定到command binding文本框(TextBox)控件上。

提示:WPF中的命令都继承自ICommand接口。ICommand暴露两个方法:Execute方法、 CanExecute方法和一个事件:CanExecuteChanged。
继承自ICommandSource的有:ButtonBase, MenuItem, Hyperlink和InputBinding。
而Button, GridViewColumnHeader,ToggleButton,RepeatButton继承自ButtonBase。 System.Windows.Input.KeyBinding和MouseBinding继承自InputBinding。
2、四个小问题:
(1)如何指定Command Sources?
XAML:(请将“ApplicationCommands.Properties”换成对应的ApplicationCommands属性值,比如:

ApplicationCommands.Copy)
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Properties" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
 
同等的C#代码:

StackPanel cmdSourcePanel = new StackPanel();
ContextMenu cmdSourceContextMenu = new ContextMenu();
MenuItem cmdSourceMenuItem = new MenuItem();

cmdSourcePanel.ContextMenu = cmdSourceContextMenu;
cmdSourcePanel.ContextMenu.Items.Add(cmdSourceMenuItem);

cmdSourceMenuItem.Command = ApplicationCommands.Properties;
 
 
(2)如何指定快捷键?
XAML代码:
<Window.InputBindings>
<KeyBinding Key="B"
Modifiers="Control"
Command="ApplicationCommands.Open" />
</Window.InputBindings>

 
C#代码:

KeyGesture OpenKeyGesture = new KeyGesture(
Key.B,
ModifierKeys.Control);

KeyBinding OpenCmdKeybinding = new KeyBinding(ApplicationCommands.Open,OpenKeyGesture);
this.InputBindings.Add(OpenCmdKeybinding);
//也可以这样(下面一句与上面两句的效果等同):
//ApplicationCommands.Open.InputGestures.Add(OpenKeyGesture);
3)如何Command Binding?

XAML代码:

<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open"
Executed="OpenCmdExecuted"
CanExecute="OpenCmdCanExecute"/>
</Window.CommandBindings>
 
C#代码:

CommandBinding OpenCmdBinding = new CommandBinding(
ApplicationCommands.Open,
OpenCmdExecuted,
OpenCmdCanExecute);

this.CommandBindings.Add(OpenCmdBinding);
 
 
具体的事件处理:
C#代码:
void OpenCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
MessageBox.Show("The command has been invoked.");
}

void OpenCmdCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}


 
(4)如何设置Command Target并进行绑定Command Binding?
XAML代码:
<StackPanel>
<Menu>
<MenuItem Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=mainTextBox}" />
</Menu>
<TextBox Name="mainTextBox"/>
</StackPanel>

 

C#代码:

StackPanel mainStackPanel = new StackPanel();
TextBox mainTextBox= new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(mainTextBox);

pasteMenuItem.Command = ApplicationCommands.Paste;
 


 
以上例子全是单条命令绑定的情形,事实上,你也可以多个按钮多条命令绑定到同一控件上,比如:

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Orientation="Horizontal" Height="25">
<Button Command="Cut" CommandTarget="{Binding ElementName=textBoxInput}" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<Button Command="Copy" CommandTarget="{Binding ElementName=textBoxInput}" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<Button Command="Paste" CommandTarget="{Binding ElementName=textBoxInput}" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<Button Command="Undo" CommandTarget="{Binding ElementName=textBoxInput}" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<Button Command="Redo" CommandTarget="{Binding ElementName=textBoxInput}" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"/>
<TextBox x:Name="textBoxInput" Width="200"/>
</StackPanel>
 
 
WPF中的控件模板(ControlTemplate)
【IT168技术文档】WPF包含数据模板和控件模板,其中控件模板又包括ControlTemplate和ItemsPanelTemplate,这里讨论一下ControlTemplate。

   其实WPF的每一个控件都有一个默认的模板,该模板描述了控件的外观以及外观对外界刺激所做出的反应。我们可以自定义一个模板来替换掉控件的默认模板以便打造个性化的控件。

    与Style不同,Style只能改变控件的已有属性值(比如颜色字体)来定制控件,但控件模板可以改变控件的内部结构(VisualTree,视觉树) 来完成更为复杂的定制,比如我们可以定制这样的按钮:在它的左办部分显示一个小图标而它的右半部分显示文本。

    要替换控件的模板,我们只需要声明一个ControlTemplate对象,并对该ControlTemplate对象做相应的配置,然后将该ControlTemplate对象赋值给控件的Template属性就可以了。

ControlTemplate包含两个重要的属性:
1,VisualTree,该模板的视觉树,其实我们就是使用这个属性来描述控件的外观的
2,Triggers,触发器列表,里面包含一些触发器Trigger,我们可以定制这个触发器列表来使控件对外界的刺激发生反应,比如鼠标经过时文本变成粗体等。

参考以下代码
<Button>
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
</ControlTemplate>
</Button.Template>
 
</Button>在上面的代码中,我们修改了Button的Template属性,我们定义了一个ControlTemplate,在 <ControlTemplate> ... </ControlTemplate>之间包含的是模板的视觉树,也就是如何显示控件的外观,我们这里使用了一个Ellipse(椭圆)和一 个TextBlock(文本块)来定义控件的外观。

    很容易联想到一个问题:控件(Button)的一些属性,比如高度、宽度、文本等如何在新定义的外观中表现出来呢?
我 们使用TemplateBinding 将控件的属性与新外观中的元素的属性关联起来Width="{TemplateBinding Button.Width}" ,这样我们就使得椭圆的宽度与按钮的宽度绑定在一起而保持一致,同理我们使用Text="{TemplateBinding Button.Content}"将TextBlock的文本与按钮的Content属性绑定在一起。
除了定义控件的默认外观外,也许我们想还定义当外界刺激我们的控件时,控件外观做出相应的变化,这是我们需要触发器。参考以下代码:
<Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" >
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Button.Template>
 
</Button>在上面的代码中注意到<ControlTemplate.Triggers>... </ControlTemplate.Triggers>之间的部分,我们定义了触发器 <Trigger Property="Button.IsMouseOver" Value="True">,其表示当我们Button的IsMouseIOver属性变成True时,将使用设置器<Setter Property="Button.Foreground" Value="Red" /> 来将Button的Foreground属性设置为Red。这里有一个隐含的意思是:当Button的IsMouseIOver属性变成False时,设 置器中设置的属性将回复原值。

你可以粘贴以下代码到XamlPad查看效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlTemplateTest" Height="300" Width="300"
>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Button Content="test btn" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" >
<Button.Template>
<ControlTemplate>
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
 
接下来的一个问题是:如果我要重用我的模板,应该怎么办呢?
你需要将模板定义为资源,其实大多数情况下,我们也是这样做的
参考以下代码:
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>
 
上面的代码将我们原来的模板定义为窗体范围内的资源,其中TargetType="Button"指示我们的模板作用对象为Button,这样在整个窗体范围内的按钮都可以使用这个模板了,模板的使用方法也很简单:
<Button Content="test btn" Template="{StaticResource ButtonTemplate}" />其中的ButtonTemplate是我们定义的模板的x:Key

你可以粘贴以下代码到XamlPad查看效果:

<Window

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

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

Title="ControlTemplateTest" Height="300" Width="300"

>


<Window.Resources>

<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">

<!--定义视觉树-->

<Grid>

<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>

<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />

</Grid>

<!--定义视觉树_end-->

<!--定义触发器-->

<ControlTemplate.Triggers>

<Trigger Property="Button.IsMouseOver" Value="True">

<Setter Property="Button.Foreground" Value="Red" />

</Trigger>

</ControlTemplate.Triggers>

<!--定义触发器_End-->

</ControlTemplate>

</Window.Resources>


<Grid ShowGridLines="True">

<Grid.ColumnDefinitions>

<ColumnDefinition Width="0.2*"/>

<ColumnDefinition Width="0.6*"/>

<ColumnDefinition Width="0.2*"/>

</Grid.ColumnDefinitions>

<Grid.RowDefinitions>

<RowDefinition Height="0.3*"/>

<RowDefinition Height="0.3*"/>

<RowDefinition Height="0.4*"/>

</Grid.RowDefinitions>

<Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" />

<Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />

<Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />

</Grid>

</Window>

 

额外提一下的是,我们也可以在触发器中,调用一个故事板来达到对事件响应时的动画效果
参考以下代码 <!--定义动画资源-->
<ControlTemplate.Resources>
<Storyboard x:Key="MouseClickButtonStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
 
</ControlTemplate.Resources>我们为模板定义了一个动画资源,此后在模板的触发器中我们就可以调用该资源来实现一个动画效果了:
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>你可以粘贴以下代码到XamlPad查看效果:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ControlTemplateTest" Height="300" Width="300"
>
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ButtonTemplate">
<!--定义视觉树-->
<Grid>
<Ellipse Name="faceEllipse" Width="{TemplateBinding Button.Width}" Height="{TemplateBinding Control.Height}" Fill="{TemplateBinding Button.Background}"/>
<TextBlock Name="txtBlock" Margin="{TemplateBinding Button.Padding}" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{TemplateBinding Button.Content}" />
</Grid>
<!--定义视觉树_end-->
<!--定义动画资源-->
<ControlTemplate.Resources>
<Storyboard x:Key="MouseClickButtonStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="faceEllipse" Storyboard.TargetProperty="Width" BeginTime="00:00:00">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="50"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3" Value="100"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<!--定义动画资源_end-->
<!--定义触发器-->
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.Foreground" Value="Red" />
</Trigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="faceEllipse">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="txtBlock">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MouseClickButtonStoryboard}"/>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
<!--定义触发器_End-->
</ControlTemplate>
</Window.Resources>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="0.6*"/>
<ColumnDefinition Width="0.2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.3*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Button Content="test btn1" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" />
<Button Content="test btn2" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
<Button Content="test btn2" Grid.Column="2" Grid.ColumnSpan="1" Grid.Row="2" Grid.RowSpan="1" Template="{StaticResource ButtonTemplate}" />
</Grid>
</Window>
 
最好的模板示例:我们知道每个控件都有自己默认的模板,这是MS编写的,如果我们能够得到这些模板的XAML代码,那么它将是学习模板的最好的示例,
要想获得某个控件ctrl的默认模板,请调用以下方法:
string GetTemplateXamlCode(Control ctrl)
{
FrameworkTemplate template = ctrl.Template;
string xaml = "";
if (template != null)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = new string(' ', 4);
settings.NewLineOnAttributes = true;
StringBuilder strbuild = new StringBuilder();
XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
try
{
XamlWriter.Save(template, xmlwrite);
xaml = strbuild.ToString();
}
catch (Exception exc)
{
xaml = exc.Message;
}
}
else
{
xaml = "no template";
}
return xaml;
}
 
 
揭露WPF SDK“不能说的秘密”
【IT168 专稿】 如果经历过.NET的1.0,1.1以及2.0版本,你就很可能发现.NET 3.0中的WPF区域中的一些文档有点不同。具体来说,WPF负责介绍几个CLR和托管代码封装方面的新概念。WPF SDK团队为在参考资料中展示这些新概念而做的努力是很大的进步,主要致力于改变,因为其它的技术也在它们的API中采用了相同或者类似的范例。

System.Reflection和.NET 3.0 SDK 

    微软用于创建托管SDK框架的“魔法”其实就是映射进程(典型的就是采用托管映射,但有时也有非托管的映射,这取决于API)。编译工具映射所有.NET 3.0的API,SDK作者就在纲要中填充映射告诉我们的关于API的消息(至少这是一个目标)。但是对于比System.Reflection本身更新 的编程概念的映射,System.Reflection做得不是特别好。你可以通过查看定制特性来得到一些更具体的信息。但对于那些像XAML一样的语 言,或者独立属性,由于System.Reflectio从1.1开始就没有本质的改变,所以不能为3.0提供好的API。如果你要编写自己的映射,你不 得不做些额外的创造工作(我不会在这里描述这个问题,因为太复杂了,而且超出了本主题的范围)。对我们的SDK开发团队来说,他们也不得不非常熟悉映射代 码。然后,其他的各种各样的人,包括一些像我们这样的程序员不得不跟上表示策略——怎么在更好,更标准的托管代码段文档中表示额外的信息,比如异常和返回 值等。
                    
    正在讨论的隐藏的秘密是参考主题部分,这部分是大量的设计会议和开发工作的结果。每一个隐藏秘密都代表着编程的一个具体方面,和WPF非常相关并且对 WPF来说是新内容。所有的信息都在SDK页面,等待你的发掘。但是,我们还没搞清楚这些突然在WPF参考文档中出现的额外段落是什么,以及他们意味着什 么。
  
   抛开这些吧。让我们来照亮这些WPF文档中隐藏的秘密。一切都会得到解释!

独立属性 

    很多时候会被问道一个问题“独立属性到底是什么?”或者一个相关的更尖锐的问题“我为什么要去关心一个东西是不是独立属性呢?”我们的答案是:独立属性总览。

    跨过这个概念上的障碍之后,下一个逻辑上的问题可能是“好吧,独立属性就是一种支持CLR属性的方式,那我该怎么分别那些属性是通过这种方式被支持的呢?”我们遇到的第一个隐藏秘密是:独立属性,和独立属性信息段。
   
    在文档中提到两种方法来判断某个给定的属性是否是独立属性:
1)当你在浏览任何类型的成员表,并且看到公共属性,读描述的时候。如果这个属性是独立属性,那么描述的最后一句就是“这是一个独立特性。”
2)假设你在说明CLR属性的SDK的主题页面。同样,在成员表的相同的描述中,你可以找到这句话“这是一个独立属性”。除了描述外,每一个独立属性的属性页面内还有一段可以被合适地成为独立属性信息段。这一段在表格中包含两点:

    一个到含有独立属性标志符的域的链接。很多属性系统API函数都需要这个标志符以执行一个属性或者获得关于它的信息。有人说,对那些只是获取或设置某个已 经存在的具体属性的应用程序编程来说,使用属性系统API并不总是必须的,因为你可以调用更简单的独立属性CLR“封装”。这样,标志符主要是和包含独立 属性的高级编程任务相关,比如处理属性元数据,或者追踪属性值。

   如果一个属性是框架层的属性,那么独立属性信息段会把元数据中“标记”为true的列出来。当独立属性被WPF框架层解释时,标记报告独立属性的某些共同 特征,尤其是子特征,比如,整体系统,数据绑定,或属性值继承。SDK报告这些标记,因为这有助于实例了解改变属性是否会强迫修改整体设计,或是否你可以 省略指定绑定的两个状态,因为这是独立属性的默认设置。
默认值

    横跨独立属性和“常规”属性的一个概念是默认值。一般来说,.NET文档会在属性值段告诉你属性的默认值,并且为保持一致性,这也是默认属性被报告给独立 属性的地方。但是,独立属性默认值实际上来自属性元数据,尽管在一个平面上CLR属性可能来自一个类或者是拓展执行。这样,通过重写属性元数据,独立属性 就可以被子类或新的属性拥有者轻易改变。在已有的WPF独立属性中偶尔会发生这种情况。当发生时,每一个类的重写都会被记录在Remark段。
Routed事件

    在你不可避免地问“什么是routed事件?”之前,可以去这里看看Routed Events Overview。现在我们解决这个问题了。
不 像独立属性,routed事件没有像“这是一个routed事件”之类的习惯性描述。基本元素(UIElement, FrameworkElement, ContentElement, FrameworkContentElement)有很多的routed事件:可能它们的事件有75%都是routed的。其它的类,像控件也会有少数的 routed事件,也许和着一些不route的标准CLR事件。为了区分一个事件是否route,你需要在事件主题看看是否有Routed事件信息段。
    Routed事件信息段是一个有如下三项的表: 

   *一个指向包含有routed 事件标志符的域的链接。和属性系统API以及独立属性标志符一样,事件系统API也需要这个标志符以接入事件。像增加或移除句柄一类的简单操作并不是必须 标志符,这也是因为routed事件会有一个“包装”,这样就可以支持CLR语言中增加/移除事件句柄的CLR语法。但,如果直接使用 AddHandler或调用RaiseEvent,就需要知道routed事件的标志符。

    *Routing策略。有三种可能:Bubbling,Tunneling,和Direct。这儿有个小秘密:如果之前查看过事件名字,那么 routing策略就总是Tunneling,这是一个惯例。但区分Bubbling和Direct就有点困难了,因为没有不同的命名习惯。所以,我们提 供了参照页的信息。Direct事件实际上没有在它的元素之外route,但他们仍然服务于WPF-specific目的,在这里有描述Routed Events Overview。
  
* Delegate类型。你可以在声明事件的语法中找到列出的delegate。我们在这里重复是为了方便,所以你可以直接跳到“写恰当的句柄”的地方。

附带属性

    严格地说,附带属性是XAML的概念,而不是WPF的。但WPF 是第一个发行的采用了XAML语言的工具。所以WPF是用文档来描述XAML语法和XAML语言的先锋。如果你是代码编写员,并看了某个附带属性的叫做 DockPanel.Dock的文档,就会注意到一些特别的东西:没有XAML语法,没有代码语法。如果浏览DockPanel类,去看映射或对象就可以 证实:对CLR没有类似于“Dock”属性的东西。甚至为了在其它的产生映射的参照中使这个页面存在,SDK开发团队不得不注入这个页面。然而,对我们这 些在标记和代码间可以随意转换的人来说,附带属性的语法段确实提供了一个到附带属性的“真实”代码API的链接,这可以成为一系列获取和设置的接入方法。 对DockPanel 类来说有 GetDock和SetDock。(不幸的是,在MSDN版本的这些页面中似乎没有这些链接;在Visual Studio和offline Windows SDKs中有…相信我...)

附属事件 

   类似地,这是一个XAML的概念,但在WPF得到具体的应用。XAML语法,但对同等代码接入来说是一个“秘密”接入方法。在附属事件情况下,这些方法的 类型是Add*Handler和Remove*Handler。例如,Mouse.MouseDown调用AddMouseDownHandler就可以 有附带到给定的UIElement实例的句柄,并可以通过RemoveMouseDownHandler移除。对实际应用来说,附属事件不一定那么重要, 因为大多数都是被UIElement以更方便的方式重新使用而已。但如果你在写一套相关的控件,或者在实现一个服务,那么附属事件就很可能进入你的视野。
XAML 语法 

   SDK文档提供了XAML各种用途下的XAML语法,比如用来实例化一个XAML类的对象元素标签,用来设置属性或附带事件句柄的属性,还有XAML的具 体概念,如content property或property element语法。另一个隐藏的秘密来了...好的,也不是那么的隐秘了,因为我以前已经在博客上写过了。XAML不一定非要被绑定到计划中。最终决定 XAML是否有效的是XAML loader,最终成功还是失败取决于一些加载动作,比如试图在组件中找到一个命名匹配的类型,调用它的默认ctor,以及返回一个实例。或者,一旦对象 存在,就是试图找到一个匹配属性名字的property,然后试图用属性值的string-to-X变换,或者甚至是更复杂的在property元素中以 子关系存在的XAML的什么东西的封装来填充它。对于保持XAML的扩展性来说是有必要的,但是这会让编写文档和静态的XAML词典更困难一点儿。其一, parser会允许你实例化一个在对象模块中没有“家”的元素。比如,大多数EventArgs子类可以在XAML中建立,因为它们可以方便的拥有一个默 认ctor(加入XAML俱乐部是如此容易....)但是,然后呢?从狭义来说,XAML是UI定义上的。广义上讲,XAML是相关的对象和你希望建立的 他们的属性的任何结构的定义。你不能把EventArgs作为任何具有可疑资源收集异常的东西的子元素。这让XAML文档进退两难:这是XAML吗,或者 不是?这个困惑经常出现。一般来说,我们用具体情况来说明XAML。有什么原因让你希望用XAML来实例化这个类吗?如果有,我们会给你看一个语法。如果 没有,就像3.0的EventArgs子类一样,我们会告诉你这并不是一个典型的应用,虽然XAML loader会在和其它面向应用的对象模型完全隔离的情况下用XAML来建立一个。

    另一个秘密:虽然没有写出语法(会说这是不适用的东西),但是在frameworks中有很多支持XAML的类。一般来说,这是因为这些类来自更早的版 本,可以在整个3.0 framework中使用,因为可能合法的XAML使用率会很大。所以,为了填补空白,用户需要知道一些XAML的技巧。对初学者,读XAML and Custom Classes就可以了。当以默认的ctor和一些其它的限定来符合XAML加载器的要求时,定制的类和已有的类没什么不同的。也去看看x:Array Markup Extension中的例子吧。这个例子讲了系统名字空间和mscorlib组件,这样你就可以吧String类作为一个直接对象进行实例化。你可以外推 这些简单的概念,并做些更有用的事情。甚至设想这个场景都有点困难,但是你们是一个充满创造性的集体:让我们看看你们能做什么!描绘一些 System.Data的结构?谁知道在pre-3.0 .NET的核心部分用了多少XAML?

没有XAML属性列表?

    SDK并不是真的有一个“所有权页面”,这是XAML特有的。你可以把property列表看作是普通的成员表的一部分,但要选出XAML属性有点困难, 因为有些是只读的,有些又不使用支持XAML的类型,等等。这里,我想我们不得不承认工具和设计者可以比只利用SDK的成员表做得更好。因为工具和设计者 有语义的优势;他们知道你刚刚实例化了什么面板子类,在元素树里面它处于什么位置等。好好地综合基于工具的只列出基本的可能性的智能,然后学会按F1来获 取SDK页面的更多的帮助信息。这是我们追赶即将来临的工具/SDK的发行的下一步。限于篇幅,不能细细讲解。

没有XAML XSD的计划?

    我以前在博客上写过这个。对一个像WPF这样有很多维的产品来说,XAML的计划很困难。但你也许已经注意到接下来的XAML综合技术(Workflow Foundation, Silverlight)确实包括了XSD类型的计划。说到WPF XAML的计划,我们仍然保持观望。
WPF图形系统
【IT168 技术文档】
    在Windows NT中,图形多媒体系统基于层次结构。应用程序与顶层的API(实际上是多个用户模式的系统DLL,比如GDI32.DLL)交互,这些系统DLL最终会 通过系统服务调用处于内核模式的系统服务。NT系统的详细信息可参考《Windows 图形编程》的第一、二章。在Windows Vista中,图形系统已经移出了内核模式,并有自己的空间--WPF。 WPF移出内核除了全面提高稳定性(内核模式中通常需要共享资源),也使得实现内核模式高级控制成为可能。
    首先,WPF不再唯一依靠GDI图形API,Vista更多的依靠Direct3D处理图形操作,这样允许更多的D3D特征,实现更酷的外观和效果。当 然,为了实现这样的效果,Vista的设计上也有所改变。Windows Vista尽量不用CPU进行显示,而是用显示卡的图形处理单元(Graphics Processing Unit, GPU),用矢量图代替位图。 Windows Vista还采用了另外一种驱动模型--Windows显示驱动模型(WDDM)。实现操作系统和图形卡GPU之间更复杂的通信。为了处理操作系统更高的3D处理请求,新的WDDM驱动类型需要更多的显示卡内存。Vista能尽可能的利用显示卡以达到最好的性能。
     现在基本了解了Windows Vista图形系统,再看看其可测量性。Windows Vista可提供非常高的终端图形,据此很多人都错误的认为:为了运行Windows Vista操作系统,必须要非常高端的图形硬件。事实上并不完全如此。Windows Vista图形系统一个关键好处就是可以检测显示卡的性能。Vista根据不同的性能和驱动模型(WDDM,XPDM)提供给用户不同的用户体验。

    在Windows NT中,图形多媒体系统基于层次结构。应用程序与顶层的API(实际上是多个用户模式的系统DLL,比如GDI32.DLL)交互,这些系统DLL最终会 通过系统服务调用处于内核模式的系统服务。NT系统的详细信息可参考《Windows 图形编程》的第一、二章。在Windows Vista中,图形系统已经移出了内核模式,并有自己的空间--WPF。
    WPF移出内核除了全面提高稳定性(内核模式中通常需要共享资源),也使得实现内核模式高级控制成为可能。首先,WPF不再唯一依靠GDI图形API, Vista更多的依靠Direct3D处理图形操作,这样允许更多的D3D特征,实现更酷的外观和效果。当然,为了实现这样的效果,Vista的设计上也 有所改变。Windows Vista尽量不用CPU进行显示,而是用显示卡的图形处理单元(Graphics Processing Unit, GPU),用矢量图代替位图。
    Windows Vista还采用了另外一种驱动模型--Windows显示驱动模型(WDDM)。实现操作系统和图形卡GPU之间更复杂的通信。为了处理操作系统更高的 3D处理请求,新的WDDM驱动类型需要更多的显示卡内存。Vista能尽可能的利用显示卡以达到最好的性能。
WPF性能优化点
【IT168 技术文档】
    在建立漂亮UI的同时,我们还需要关注应用程序的性能,WPF尤其如此。下面从MS的文档中总结出了一些有用的性能优化点。在实际编写的过程中,可以参考。这个Post非完全原创,是根据一些文档总结出来的。

    1、建立逻辑树的时候,尽量考虑从父结点到子结点的顺序构建。因为当逻辑树的一个结点发生变化时(比如添加或删除),它的父结点和所有的子结点都会激发Invalidation。我们应该避免不必要的Invalidation。

    2、当我们在列表(比如ListBox)显示了一个CLR对象列表(比如List)时,如果想在修改List对象后,ListBox也动态的反映这种变 化。此时,我们应该使用动态的ObservableCollection对象绑定。而不是直接的更新ItemSource。两者的区别在于直接更新 ItemSource会使WPF抛弃ListBox已有的所有数据,然后全部重新从List加载。而使用ObservableCollection可以避 免这种先全部删除再重载的过程,效率更高。

    3、在使用数据绑定的过程中,如果绑定的数据源是一个CLR对象,属性也是一个CLR属性,那么在绑定的时候对象CLR对象所实现的机制不同,绑定的效率也不同。

    A、数据源是一个CLR对象,属性也是一个CLR属性。对象通过TypeDescriptor/PropertyChanged模式实现通知功能。此时绑定引擎用TypeDescriptor来反射源对象。效率最低。
    B、数据源是一个CLR对象,属性也是一个CLR属性。对象通过INotifyPropertyChanged实现通知功能。此时绑定引擎直接反射源对象。效率稍微提高。
    C、数据源是一个DependencyObject,而且属性是一个DependencyProperty。此时不需要反射,直接绑定。效率最高。

    4、访问CLR对象和CLR属性的效率会比访问DependencyObject/DependencyProperty高。注意这里指的是访问,不要和 前面的绑定混淆了。但是,把属性注册为DependencyProperty会有很多的优点:比如继承、数据绑定和Style。所以有时候我们可以在实现 DependencyProperty的时候,利用缓存机制来加速访问速度:看下面的缓存例子:
public static readonly DependencyProperty MagicStringProperty =
DependencyProperty.Register("MagicString", typeof(string), typeof(MyButton), new PropertyMetadata(new PropertyInvalidatedCallback(OnMagicStringPropertyInvalidated),new GetValueOverride(MagicStringGetValueCallback)));
private static void OnMagicStringPropertyInvalidated(DependencyObject d)
{
// 将缓存的数据标识为无效
((MyButton)d)._magicStringValid = false;
}
private static object MagicStringGetValueCallback(DependencyObject d)
{
// 调用缓存的访问器来获取值
return ((MyButton)d).MagicString;
}
// 私有的CLR访问器和本地缓存
public string MagicString
{
get
{
// 在当前值无效时,获取最新的值保存起来
if (!_magicStringValid)
{
_magicString = (string)GetValueBase(MagicStringProperty);
_magicStringValid = true;
}
return _magicString;
}
set
{
SetValue(MagicStringProperty, value);
}
}
private string _magicString;
private bool _magicStringValid;
 
    另外,因为注册的DependencyProperty在默认是不可继承的,如果需要继承特性,也会降低DependencyProperty值刷新的效 率。注册DependencyProperty属性时,应该把DefaultValue传递给Register方法的参数来实现默认值的设置,而不是在构 造函数中设置。

    5、使用元素TextFlow和TextBlock时,如果不需要TextFlow的某些特性,就应该考虑使用TextBlock,因为它的效率更高。

    6、在TextBlock中显式的使用Run命令比不使用Run命名的代码要高。

    7、在TextFlow中使用UIElement(比如TextBlock)所需的代价要比使用TextElement(比如Run)的代价高。

    8、把Label(标签)元素的ContentProperty和一个字符串(String)绑定的效率要比把字符串和TextBlock的Text属性 绑定的效率低。因为Label在更新字符串是会丢弃原来的字符串,全部重新显示内容。

    9、在TextBlock块使用HyperLinks时,把多个HyperLinks组合在一起效率会更高。看下面的两种写法,后一种效率高。

A、
<TextBlock Width="600" >
<Hyperlink TextDecorations="None">MSN Home</Hyperlink>
</TextBlock>
<TextBlock Width="600" >
<Hyperlink TextDecorations="None">My MSN</Hyperlink>
</TextBlock> 
 
B、
<TextBlock Width="600" >
<Hyperlink TextDecorations="None">MSN Home</Hyperlink>
<Hyperlink TextDecorations="None">My MSN</Hyperlink>
</TextBlock> 
 
    10、任与上面TextDecorations有关,显示超链接的时候,尽量只在IsMouseOver为True的时候显示下划线,一直显示下划线的代码高很多。

    11、在自定义控件,尽量不要在控件的ResourceDictionary定义资源,而应该放在Window或者Application级。因为放在控件中会使每个实例都保留一份资源的拷贝。

    12、如果多个元素使用相同的Brush时,应该考虑在资源定义Brush,让他们共享一个Brush实例。

    13、如果需要修改元素的Opacity属性,最后修改一个Brush的属性,然后用这个Brush来填充元素。因为直接修改元素的Opacity会迫使系统创建一个临时的Surface。

    14、在系统中使用大型的3D Surface时,如果不需要Surface的HitTest功能,请关闭它。因为默认的HitTest会占用大量的CPU时间进行计算。 UIElement有应该IsHitTestVisible属性可以用来关闭HitTest功能。
WPF中的Style
【IT168 技术文档】
    Style是一种修改属性值是方法。我们可以将其理解为对属性值的批处理。对批处理大家应该不会感到默认。对,通过Style我们可以批量修改属性的值。先从一个简单的Style例子开始: 
<Window x:Class="Viewer3D.WindowSettins"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Viewer3D Settings"
>
<Window.Resources>
<Style TargetType="CheckBox">
<Setter Property="Height" Value="20"/>
<Setter Property="Width" Value="50"/>
<EventSetter Event="Checked" Handler="Checked_Click"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Window.Resources>
</Window> 
 
   第一感觉你可能会奇怪,为什么Style在资源里呢?我个人直接将理解为“批处理”的缘故。因此Style是修改多个对象的属性值,它不从属于单独的元素 对象。另一个疑惑的问题是Style没有设置x:Key属性。这是一个非常关键的设置。如果我们设置了Style的x:Key属性,相当于在当前 Window是资源中定义了一个名称为x:Key设定值的Style对象。记住定义的效果相当于对象。如果没有设置x;Key,那么这个Style将对属 于这个Window中所有CheckBox生效。这就起到了批处理的效果。

    首先设定的是Style的TargetType属性,它表示我们希望修改的目标类型。然后定义一个Setters的集合。每个Setter都表示修改的一 个属性或者事件。Property设置属性名称,Value设置属性值。Event设置事件名称,Handler设置事件的响应函数名称。只要你在 Resource做了类似的定义,在此Window中所使用的任何ChekcBox都会默认这些属性值。是不是很方便呢?我们在此定义一次,可以节省很多 代码。

    也许你还会问:这样的统一修改属性太武断、霸道了吧!也许是的。我们只修改部分Element的属性值,而希望对某些特殊的Element做特殊处理。这样的需求WPF当然也是支持的。看看下面的代码:
<Style BasedOn="{StaticResource {x:Type CheckBox}}"
TargetType="CheckBox"
x:Key="WiderCheckBox">
<Setter Property="Width" Value="70"/>
</Style> 
 
    WPT通过BasedOn对这种特殊的Style提供了支持。很明显,BasedOn的意思是我们当前的Style基于在资源的CheckBox。这里又 看到了x;Key扩展标记。因为我们需要的是一个特例,一个特殊的Style对象。为了以后引用这个Style,我们需要x:Key的标识作用。其它的代 码与前面类似。

    定义后,引用这个特殊Style的CheckBox的代码是这样的:
<CheckBox Style="{StaticResource WiderCheckBox}">Win</CheckBox> 
 
    你已经看到,我们在CheckBox中指定了Style属性,并引用前面的StaticResource标记。
WPF与Win32
【IT168 技术文档】
    GDI是当今应用程序的主流图形库,GDI图形系统已经形成 了很多年。它提供了2D图形和文本功能,以及受限的图像处理功能。虽然在一些图形卡上支持部分GDI的加速,但是与当今主流的Direct3D加速相比还 是很弱小。GDI+开始出现是在2001年,它引入了2D图形的反走样,浮点数坐标,渐变以及单个象素的Alpha支持,还支持多种图像格式。但是, GDI+没有任何的加速功能(全部是用软件实现)。

    当前版本的WPF中,对一些Win32功能还没有很好的支持,比如WMF/EMF文件,单个象素宽度的线条等等。对于这些需求还需要使用GDI/GDI+来实现。

    在Windows Vista中,GDI和GDI+仍然支持,它们与WPF并行存在,但是基本上没有任何功能性的改进。对GDI和GDI+的改进主要集中在安全性和客户相关 问题上。WPF的所有提交都不依赖于GDI和GDI+,而是Direct3D。并且所有的Primitive都是通过Direct3D的本地接口实现的。 还记得我前面随笔中提到过的Milcore吗?它就是和Direct3D交互的非托管代码组件。由于WPF的大部分代码都是以托管代码的形式存在的,所以 WPF中有很多托管、非托管的交互。当然,在一些图形卡不支持WPF所需要的功能时,WPF也提供了稍微低效的软件实现,以此来支持在某些PC上运行 WPF应用程序。

    在Windows Vista中,Direct3D的关键改进就是引入了新的显示驱动模型。VDDM驱动模型虚拟化了显卡上的资源(主要是显示内存),提供了一个调度程序, 因此多个基于Direct3D的应用程序可以共享显卡(比如WPF应用程序和基于WPF的Windows Vista桌面窗口管理)。VDDM的健壮性、稳定性也得到了提高,大量的驱动操作从内核(Kernel)模式移动到了用户(User)模式,这样提高了 安全性,也简化了显示驱动的开发过程。

    在Windows Vista中存在两个版本的Direct3D:Direct3D 9和Direct3D 10。WPF依赖于Direct3D 9,这样能更广泛的解决兼容性问题。另外一个非常重要的原因就是为Vista的服务器版本提高方便,因为服务器版本的Vista对显卡和Direct3D 基本上没有任何的要求。同时WPF也支持Direct3D 10。Direct3D 10依赖与VDDM,只能在Windows Vista上使用。由于Windows XP没有VDDM,虽然Microsoft做了很大的努力来改善XP中Direct3D 9相关驱动,提高内容的显示质量,但是由于XP中没有对显卡资源的虚拟化,强制所有的应用程序都用软件提交。

    WPF对某些多媒体的功能支持还需要依赖老的技术,比如DirectShow。当我们进行音频视频的捕捉或者其它任务时,只能直接用DirectShow实现,然后再用HwndHost嵌入到WPF内容当中。

    利用类似的技术,我们可以在WPF应用程序中显示自定义格式的内容。通过提供自定义的DirectShow CODEC,然后用Media元素实现和WPF内容毫无限制的集成。

    另外,WPF对XPS等文档的打印输出也得到了极大的改善。XPS文档本身的规范也极大的提高了其打印的质量,XPS文档的规范可以参考MSDN的资料。 除了打印,Vista操作系统中对远程的改进也部分依赖于WPF,比如有远程协助、远程桌面和终端服务等等。它们的实现过程是通过发送一系列的“远程”命 名到客户端,客户根据自己PC的性能和命名进行显示,这样显示的质量能得到极大的提高。

    在WPF中,对Direct3D进行各种封装。当然,如果你本身对Direct3D/OpenGL很熟悉,也可以直接在WPF中使用。封装后的 Direct3D更容易使用。并且在Web应用程序(XBAP)也可以使用Direct3D。在WPF中使用的Direct3D,没有直接用非托管代码控 制所拥有的灵活性,也不能直接对硬件进行底层控制。

    WPF中所有的提交都是矢量形式的,我们可以对图像或窗口进行任意级的放缩,而图像的质量不会有任何的损耗。
WPF的逻辑树和视觉树
【IT168 技术文档】
    这部分的内容来自于即将出版的新书《WPF Unleashed》的第三章样章。关于什么是逻辑树,我们先看下面的一个伪XAML代码的例子:
<Window ......>
<StackPanel>
<Label>LabelText</Lable>
</StackPanel>
</Window> 
 
    在这样一个简单UI中,Window是一个根结点,它有一个子结点StackPanel。而StackPanel有一个子结点Label。注意Label 下还有一个子结点string(LabelText),它同时也是一个叶子结点。这就构成了窗口的一个逻辑树。逻辑树始终存在于WPF的UI中,不管UI 是用XAML编写还是用代码编写。WPF的每个方面(属性、事件、资源等等)都是依赖于逻辑树的。

    视觉树基本上是逻辑树的一种扩展。逻辑树的每个结点都被分解为它们的核心视觉组件。逻辑树的结点对我们而言基本是一个黑盒。而视觉树不同,它暴露了视觉的 实现细节。下面是Visual Tree结构就表示了上面四行XAML代码的视觉树结构:




    并不是所有的逻辑树结点都可以扩展为视觉树结点。只有从System.Windows.Media.Visual和 System.Windows.Media.Visual3D继承的元素才能被视觉树包含。其他的元素不能包含是因为它们本身没有自己的提交 (Rendering)行为。

    在Windows Vista SDK Tools当中的XamlPad提供查看Visual Tree的功能。需要注意的是XamlPad目前只能查看以Page为根元素,并且去掉了SizeToContent属性的XAML文档。如下图所示:



    注意图中工具栏特别标记的地方。我们可以看到Visual Tree确实比较复杂,其中还包含有很多的不可见元素,比如ContentPresenter。Visual Tree虽然复杂,但是在一般情况下,我们不需要过多地关注它。我们在从根本上改变控件的风格、外观时,需要注意Visual Tree的使用,因为在这种情况下我们通常会改变控件的视觉逻辑。

    WPF中还提供了遍历逻辑树和视觉树的辅助类:System.Windows.LogicalTreeHelper和 System.Windows.Media.VisualTreeHelper。注意遍历的位置,逻辑树可以在类的构造函数中遍历。但是,视觉树必须在经 过至少一次的布局后才能形成。所以它不能在构造函数遍历。通常是在OnContentRendered进行,这个函数为在布局发生后被调用。

    其实每个Tree结点元素本身也包含了遍历的方法。比如,Visual类包含了三个保护成员方法VisualParent、 VisualChildrenCount、GetVisualChild。通过它们可以访问Visual的父元素和子元素。而对于 FrameworkElement,它通常定义了一个公共的Parent属性表示其逻辑父元素。特定的FrameworkElement子类用不同的方式 暴露了它的逻辑子元素。比如部分子元素是Children Collection,有是有时Content属性,Content属性强制元素只能有一个逻辑子元素。
WPF中的命令与命令绑定
【IT168 技术文档】
    说到用户输入,可能我们 更多地会联想到键盘、鼠标、手写笔,其实还用一种高级别的输入——命令(Commands),从WPF类库角度讲他们分别对于Keyboard, Mouse,Ink与ICommand。命令是一种语义级别的输入而不是设备级别的,比如“复制”与“粘贴”,但实现一个命令可以有很多中方式,比如“粘 贴”,我们可以使用CTRL-V,也可以使用主菜单或右键菜单(上下文菜单)等等。在以往的.net版本中,要在软件界面上添加一个“粘贴”按钮,是非常 麻烦的事情,你得监视剪切板中是否有可用的文本以及对应的文本框是否获得了焦点以便启用或禁用该按钮,当粘贴时你还得从剪切板中取得相应的文本并插入到文 本框的合理位置,等等。

    在WPF中提供的命令机制能非常简单地实现这些任务,下面的Demo演示了如何简单到不用手动编写一行后台逻辑代码便解决上面的难题的,你可以粘贴下面的代码到XamlPad:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="Window1"
Width="640" Height="480">
<DockPanel LastChildFill="True">
<Menu Width="Auto" Height="20" DockPanel.Dock="Top">
<MenuItem Command="ApplicationCommands.Copy" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
<MenuItem Command="ApplicationCommands.Paste" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
<MenuItem Command="ApplicationCommands.Cut" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
<MenuItem Command="ApplicationCommands.Redo" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
<MenuItem Command="ApplicationCommands.Undo" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
</Menu>
<RichTextBox>
<FlowDocument>
<Paragraph/>
</FlowDocument>
</RichTextBox>
</DockPanel>
</Window> 
 


    Demo中菜单栏的菜单项不仅仅能完美地完成任务而且能根据文本框的状态和剪切板自动的启用与禁用,而我们却没有为这些复杂的逻辑编写任何的后台代码。这就是WPF中的命令机制为我们提供了方便。

    注意这一行代码:
<MenuItem Command="ApplicationCommands.Copy" Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/> 
 
    我们将“复制”命令(ApplicationCommands.Copy)赋值给了菜单项的Command属性,实现了ICommandSource接口 的元素都拥有该属性,这表示该元素可以作为一个“命令源”来引发某个命令,其Command属性就指示了其将引发的命令。

    其实一个命令系统是被分为四个部分的:
    Command(命令):一个语义级别的输入,比如“复制”,“左对齐”,“播放”等
    CommandSource(命令源):引发某命令的元素,比如按钮,菜单项,键盘(Ctrl-C,F1等),鼠标等。
    CommandTarget(命令目标):命令被作用的目标,比如文本框,播放器等。
    CommandBinding(命令绑定):用于将命令和命令的处理逻辑链接起来,比如同样的"粘贴",但粘贴文本和粘贴图片的处理逻辑是不一样的,命令绑定负责将“粘贴”命令与合理的处理逻辑连接起来。
    关于命令系统将在本文章的后续部分中讲解,不过值得一提的是,在上面的Demo中我们只指定了命令和命令源,并未指定命令目标,但它会以获取键盘焦点的元 素(这里是我们的RichTextBox)作为默认值,而命令绑定以及命令的后台执行逻辑被隐藏到了RichTextBox内部,那些编写 RichTextBox控件的开发人员会为我们编写该部分代码。

    另外,你可能已经发现,在Demo中我们并没有为菜单项标题直接设置“复制”“粘贴”这样的文本,而是使用了如下的一个绑定:
Header="{Binding Path=Command.Text, RelativeSource={RelativeSource Self}}"/>
 
    我们将菜单文本绑定到了命令的Text属性,这是因为,如果一个命令为RoutedUICommand类型,那么该命令将有一个Text属性来说明该命令 对应到的文本名称,该Text属性会自动本地化的,也就是说如果你的计算机使用语言是简体中文的话该菜单项显示的是“复制”,如果你的计算机使用的语言是 英语的话该菜单项显示的将是“Copy”。

    WPF为我们提供了大量内置命令,包括ApplicationCommands,NavigationCommands,,MediaCommands, EditingCommands与ComponentCommands,以及控件开发人员为它们的控件也提供了很多特有的命令(比如 Slider.DecreaseLarge 与 Slider.DecreaseSmall),这些足以应付平时的大多数应用,如果还不够的话,你可以为自己的应用自定义更多的命令。
在WPF中,命令(Commanding)被分割成了四个部分,分别是ICommand,ICommandSource,CommandTarget和CommandBinding。下面我们来分别探讨这四个部分。

    1,ICommand
    Command也就是我们的“命令”本身,比如“复制”“粘贴”。在WPF中,所有的命令都必须实现ICommand接口,它为所有的命令提供一个抽象, 这个抽象对于我们实现Undo、Redo操作非常重要,如果你学习一下设计模式中的“命令”模式,你会更加深刻的理解。
    ICommand接口中拥有Execute()方法,该方法用于命令的执行(不过,注意:命令的执行逻辑——比如将剪切板中的文本去出来放到文本框的合适 位置——并没有被编写到该方法中,稍后我们会讲到这其中的奥妙),另外的一个方法是CanExecute()用于指示当前命令在目标元素上是否可用,当这 种可用性发生改变时其便会引发该接口的尾页一个事件CanExecuteChanged。
    在目前的WPF类库中,你能看到唯一一个实现了ICommand接口的类型RoutedCommand(其实还有一个名为SecureUICommand 的类也实现了该接口,不过该类未被公开),“Routed”是一个不太容易被翻译的修饰词(有人将它翻译为“路由”),但这意味着该类型的命令可以向 WPF中的RoutedEvent一样在元素树中上下传递。
RoutedCommand的子类RoutedUICommand是我们经常使用 的类型,它与RoutedCommand的不同之处仅仅在与它多了一个Text属性来描述该命令,不过大多数WPF内置命令的Text属性有一个很不错的 特点:其支持自动本地化。这至少会为我们的软件的本地化减少工作量。 

     2,ICommandSource与CommandTarget
    命令源,用来触发我们的命令,比如用一个菜单项来触发“复制”命令,那么该菜单项就是命令源。要使一个元素成为命令源,其必须实现 ICommandSource接口。命令源决定了它所要触发的命令、该命令所作用的对象以及命令参数(如果需要的话),这分别对应于它的三个属性: Command、CommandTarget以及CommandParameter。其中需要注意的是CommandTarget,因为在WPF中如果你 不为命令源指定其命令对象,那么其将会把界面上获得键盘焦点的元素作为默认的命令对象,这为我们提供了方便,比如界面上有两个文本框,我们不必担心主菜单 项上的“粘贴”操作是针对哪个文本框的,谁获得焦点便针对谁,这符合大家的习惯。但引入的问题是,如果命令目标不具备获取键盘焦点的能力(比如 Label)或命令源会抢占焦点(比如用Button来代替菜单项,点击按钮时焦点由文本框转移到了按钮上),你的命令将会无效,这时你就必须为命令源指 定命令目标。

    3,CommandBinding
    前面已经提到我们并没有将命令的执行逻辑编写到其Excute()方法中,这是有道理的,比如"粘贴"命令 (ApplicationCommands.Paste),粘贴一段文本到文本框和粘贴一个图片到绘图板的执行逻辑肯定是不一样的,负责开发该“粘贴”命 令的开发人员不可能知道所有的粘贴操作的具体逻辑,使用“粘贴”命令的客户也不应该为该执行逻辑负责,编写该执行逻辑的任务应该被分发给那些支持“粘贴” 操作的控件的开发人员以及那些希望为自己的控件添加“粘贴”操作的客户。也就是说我们需要将“行为的请求者(命令)”和“行为的执行者(命令的执行逻 辑)”分开而实现一种松耦合,而CommandBinding(命令绑定)便是命令和命令执行逻辑的桥接器。
    我们使用CommandBinding将命令与其合适的执行逻辑绑定在一起: 

CommandBinding CloseCommandBinding = new CommandBinding(

ApplicationCommands.Close, CloseCommandHandler, CanExecuteHandler);
 
    CommandBinding构造方法的最后两个参数分别是ExecutedRoutedEventHandler 与 CanExecuteRoutedEventHandler 类型的委托,用于指示如何执行命令和如何判断命令能否被执行。
    与CommandBinding一样扮演着中间角色的还有CommandManager类,它为命令绑定(以及输入绑定)提供了很多实用方法。
WPF中的传递事件
【IT168 技术文档】
    传递事件

    WPF在.NET简单事件通知之上添加了很多基础结构。传递事件的设计使得事件可以与元素树一起很好的工作。事件发生后,可以在视觉树和逻辑树自动地进行上下传递,我们不需要添加任何额外的代码。

    传递事件使得我们不需要过多关注于视觉树,这样封装对于我们理解WPF的元素合成非常重要。比如,我们点击一个按钮的事件,在点击的时候我们实际上点击的 是一个ButtonChrome或者TextBlock,也就是说我们点击的是Button的内容元素。正是因为事件可以沿视觉树传递,Button才发 现这个事件,并且可以处理。因此,我们可以给Button的Content当中添加任意的元素,而不会对事件有任何的影响。如果没有这样的事件传递,我们 点击Button内的元素时,必须手动编写代码触发Button点击事件。

    传递事件的的实现和行为与Dependency属性类似。同样,我们看看如何实现简单的传递事件。多数时候,传递事件并不比普通的.NET事件难。与 Dependency属性一样,.NET语言(除了XAML)本身并不明白传递目标。这些支持都是基于WPF API。
public class Button
{
// 传递的事件
public static readonly RoutedEvent ClickEvent;
static Button()
{
// 注册事件
Button.DoubleClickEvent = EventManager.RegisterRoutedEvent(“Click”,
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button));
…
}
// .NET事件保证 (可选的)
public event RoutedEventHandler Click
{
add { AddHandler(Button.ClickEvent, value); }
remove { RemoveHandler(Button.ClickEvent, value); }
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
…
// 激发事件
RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));
…
}
…
}
 
    从上面的实现可以看出,事件与Dependency属性有很多相似之处。也是定义一个静态的RoutedEvent成员,同样在静态构造函数里注册事件。 为了方便,也包装了一个普通的.NET事件。这里的AddHandler/RemoveHandler不是从DependencyObject派生,而是 更高一级的基类System.Windows.UIElement。这两个方法为相应的事件添加/删除一个委派。在 OnMouseLeftButtonDown中,我们构造一个事件参数,传入事件源对象this,然后调用RaiseEvent函数。

    事件策略和处理函数

    注册WPF事件时,我们需要为传递事件选择一种策略,这个策略指定了事件在元素树中传递的方式。WPF支持这样三种策略:

    Tunneling:事件首先在根元素激发,然后到达树下的每个元素直到源元素(或者有处理函数处理这个事件终止了传递)。
    Bubbling:事件首先在源元素激发,然后向上直到根元素(或者有处理函数处理这个事件终止了传递。
    Direct:事件只在源元素激发。这与普通的.NET事件一样,除了参与事件触发器。

    在上面的例子中,我们注册的事件策略就是Bubbling。

    传递事件的处理函数的参数与普通.NET事件一样。第一个参数System.Object表示处理函数依附的元素。第二个的System.EventArgs派生类,提供了如下四个有用的属性:

    Source:逻辑树中激发事件的原始元素。
    OriginalSource:视觉树中激发事件的原始元素。
    Handled:布尔值,表示事件是否被处理。
    RoutedEvent:实际的传递事件对象(比如Button.ClickEvent)。这个对于相同的处理函数处理多个传递事件时非常有用,可以用来区别传递事件。
    Source和OriginalSource代表了逻辑树和视觉树对象。这有利于我们进行一些低级控制,但是对于有的事件,不需要区别它们,这两个的值是相同的。
现在,我们看看WPF到底是如何处理Bubbling和Tunneling事件的。最后介绍了Attached事件。

    在UIElement类,预定义了很多的传递事件,比如键盘、鼠标等等。其中大多数是Bubbling事件,其中很多的事件都还有一个对应的 Tunneling事件。所有的Tunneling事件都是Preview前缀命名,它们都在对应的Bubbling事件之前激发。比如 PreviewMouseMove这个Tunneling事件是在MouseMove这个Bubbling事件之前激发的。

    Tunneling事件的好处就是可以有机会改变或者取消后面的Bubbling事件。WPF内建的响应事件只会对Bubbling事件进行响应,当然, 前提了Bubbling和Tunneling同时定义。这种行为有什么好处呢?看下面的一个例子:比如,我们想实现一种特殊的编辑框,只允许输入一些特定 的字符。以前的实现方法在处理编辑框的KeyDown或者编辑框的WM_CHAR事件,然后判断新输入的字符是否满足条件,如果不满足,我们再把编辑框的 值设置为原来的值。这种实现技术会有字符的一个回退过程。而在WPF中,实现方法不同,直接在PrevewKeyDown等Tunneling事件中处 理,如果是不需要的字符,把事件设置为已经处理过。这样这个事件就不会进入到后面的Bubbling事件KeyDown中,WPF也根本不会显式这个字 符。这种方法的效果将比之前的回退处理好很多。

    虽然我们可以通过RoutedEventArgs参数的Handled属性为True来终止事件的传递。但是,有时候我们需要某个事件始终被接受处理,这 可以通过程序代码实现。使用重载的AddHanlder方法。比如,我们给窗口添加一个鼠标右键的处理方法(其中MRBD_Handler是类的一个事件 方法):
public AboutDialog()
{
InitializeComponent();
this.AddHandler(Window.MouseRightButtonDownEvent,
new MouseButtonEventHandler(MRBD_Handler), true);
}
 
    这样,任何条件下,MRBD_Handler都可以接收到窗口的鼠标右键事件。即使鼠标右键是点击在窗口中的某个子控件之上。

    Attached事件
    与Attached属性类似,WPF的Element在事件没有定义的情况下也支持Tunneling或者Bubbling事件。比如,我们可以在一个简单的窗口程序中这样指定事件函数:
<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
x:Class=”Window1”
Button.Click=”Button_Click”
<Button Text="TestButton" Width="50" Height="30">
</Window> 
 
    例子中,因为Window本身没有定义Click事件,所以我们必须指定Click事件属性的名称前缀,也就是定义事件的类名。经过这样的定义后,点击在 Window中的TestButton,也会激发属性声明的Click事件,调用对应的Button_Click方法。

    为什么这样的定义可以通过呢?首先编译时,XAML会看到Button类确实定义了一个Click的.NET事件。在运行时,会直接调用 AddHandler把这两个事件依附到Window对应的类当中。所以上面用XAML属性声明的事件代码与下面的程序代码等效:
public Window1
{
InitializeComponent();
this.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click));
}
 
WPF的Attached属性
【IT168 技术文档】
    本篇,我们再继续看一种特殊的Dependency属性:Attached属性。Attached属性可以非常高效地Attach到其他的对象中。
我们仍然用前面的一个简单XAML代码为例:
<Window>
<StackPanel>
<Label>LabelText</Lable>
</StackPanel>
</Window> 
 
    现在,如果需要对StackPanel及其子元素设置字体大小,应该如何做呢?在Window元素中,它有一个属性FontSize,可以直接设置。但 是,StackPanel自己本身并没有FontSize这样的属性。这就该Attached属性出场了。这里我们需要用定义在TextElement元 素中的Attached属性FontSize来设置StackPanel的字体。
<Window>
<StackPanel TextElement.FontSize=”30”>
<Label>LabelText</Lable>
</StackPanel>
</Window> 
 
    这样,StackPanel的子元素就能通过属性值继承得到新的FontSize属性。对于这样的XAML代码,XAML编译器或者解析器看到这种语法 时,就要求TextElement(有时也称为Attached属性提供者)有相应的静态方法SetFontSize来设置对应的属性值。因此,上面的 Attached属性设置代码,可以如下用C#实现:
StackPanel panel = new StackPanel();
TextElement.SetFontSize(panel, 30);
 
    从这里的代码可以看出,Attached属性并不神秘。只是调用方法把元素和不相关的属性关联起来。而SetFontSize实现也比较简单。它只是调用 了Dependency属性访问函数所调用的DependencyObject.SetValue方法。注意调用的对象是传入的 DependencyObject,而不是当前的实例:
public static void SetFontSize(DependencyObject element, double value)
{
element.SetValue(TextElement.FontSizeProperty, value);
}
 
    同样地,Attached属性也定义了对应的GetXXX函数。它调用的DependencyObject.GetValue方法:
public static double GetFontSize(DependencyObject element)
{
return (double)element.GetValue(TextElement.FontSizeProperty);
}
 
    与普通的Dependency属性一样,这些GetXXX和SetXXX方法除了实现对GetValue和SetValue的调用,不能做任何其他额外的工作。

    其实,在WPF应用中,Attached属性更多的用来控制UI的布局。除了前面的StackPanel,还有Grid等等。

    补充说明:上面的代码还有一个问题需要说明。我们设置StackPanel的字体属性时用的是TextElement元素。为什么不用其他的元素Control、Button呢?

    这个问题的关键之处在于Dependency属性的注册方法。我曾在Dependency属性[1]做过简单的说明。我们看看Element的FontSizeProperty属性的注册代码:
TextElement.FontSizeProperty = DependencyProperty.RegisterAttached(
“FontSize”, typeof(double), typeof(TextElement), new FrameworkPropertyMetadata(
SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(TextElement.IsValidFontSize));
 
    这里与我们前面的IsDefault属性类似,只是RisterAttached方法优化了Attached属性需要的属性元数据的处理过程。

    另一方面,Control的FontSize属性是在TextElement元素已经注册的属性之上调用AddOwner方法,获取一个完全相同的实例引用:
Control.FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(Control), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize,
FrameworkPropertyMetadataOptions.Inherits));
 


    所以,在实现Attached属性时我们使用的是TextElement,而不是Control等等。
WPF的Dependency Property System
【IT168 技术文档】
    今天我们来看看WPF的 基础:Dependency Property System。在WPF中,所有的属性(可以在XAML进行设置的属性)都依赖于此功能,所以说他是WPF的基础我想并不为过吧。.在WPF中, Dependency Property System为我们提供了一个访问XAML中设置的属性的入口,通过他我们可以为属性设置默认的值,设置属性的表达式、值的范围等,还可以通过他获得属性 改变的事件。
    Dependency Property System在框架中表现为DependencyProperty类。
    所有可以在XAML中使用的属性都必须用DependencyProperty类中的Register方法进行注册(在以后我们开发自定义控件的时候将会频繁的使用到它)。
    如果我们想在自定义的控件中使用自定义的类型需要完成以下2步:(此段内容翻译自Win SDK)
    1. 使用Register方法来注册自定义类型。方法将返回一个DependencyProperty对象,用于接收此返回值的变量必须是
静态只读(static readonly)的,且变量的名字必须已Property结尾,而他的访问域应该是public.
    例如:在自定义Class1中你要注册一个自定义类型:Test
public static readonly DenpendencyProperty TestProperty = DenpendencyProperty.Register(“Test”,
typeof(Test),typeof(Class1));
 
    Register的另外一个重载将接受一个PropertyMetadata类型的参数,用于实现对属性值进行限制,接收事件更改通知等功能。
    2. 为程序提供访问的入口。所谓的入口就是我们一般的属性。例:
public Test SampleTest
{
get
{
return this.GetValue(TestProperty);
}
set
{
this.SetValue(TestProperty, value);
}
}
 


    今天只是简单的介绍了下Dependency Property System。目前3.0资料还不多,希望我的介绍对大家学习3.0有点帮助。
明天我将会给出一个简单的例子。等不及的朋友可以用Reflector去反编译3.0的类库,随便找个控件看看。推荐:DockPanel
注:本文基于.Net FrameWork 3.0正式版,参考Win SDK _Vista_RC1 。文章为目前所学知识的总结,随着学习的深入,将会随时更改文章的内容。
WPF指南之XAML的名字空间
【IT168 技术文档】
    xmlns的作用是设置XML文件的命名空间。类似的,xmlns:x的作用也是指定命名空间。这里为什么是x而不是其他的,我们可以简单的理解为其只是MS的一个命名而已,没有任何特殊的意义,当然,为了避免和它的冲突,我们定义自己的命名空间的时候不能是x。
    而另一个x:Class的作用就是支持当前Window所对应的类,前面已经说过每个XAML元素都是一个CLR类型,这里的x:Class是 Window的一个属性,属性的内容指出当前的窗口类是FirstXAML名字空间下的Windows1。为什么需要类,而不全部用XAML实现? XAML的主要作用还是编写UI部分,我们仍然需要用代码对程序逻辑进行更深层次的控制。
    好了,这是两个最基本的名字空间。同样地,名字空间也可以自定义,并且这个自定义会给我们带来很大的方便。我们定义如下的一个类:

namespace DataBind4Image


{

public class GroupData


{

//具体的细节忽略

}

}

 
    如果想在XAML文件中使用这个GroupData类对象,我们就可以通过自定义的名字空间引入这个类:

xmlns:local="clr-namespace:DataBind4Image"
 
    这里的后缀local只是一个标识,你可以设置为任何你喜欢的唯一标识。通过这个引入定义我们就可以在XAML文件中用local来标识 DataBind4Image当中的任何类。访问GroupData类时只需要加上local就可以识别了:<local: DrawingGroupData/>
利用名字空间,除了可以引入我们定义的当前工程的类,还可以引入任何的Assembly。直接看例子是最简单的:

<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=System"

>

<ListBox>

<sys:String>One</sys:String>

</ListBox>

</Window> 

 
    例子当中引入.NET的System Assembly,通过它我们就可以直接使用System的任何类。利用这种类似的方式,我们可以在XAML中使用几乎所有的DOTNET框架类。
    最后说明一下在XAML中inline嵌入程序逻辑处理代码的情况。利用<CDATA[…]]>关键字引入处理代码。这种情况在实际当中不太 合适,我们不应该采用UI和逻辑混合的方式。详细的解释可以参数Windows SDK文档。

<![CDATA[

void Clicked(object sender, RoutedEventArgs e)

{

button1.Content = "Hello World";

}

]]></x:Code> 
 
    前面提到过每个XAML元素表示一个.NET CLR类。多数的XAML元素都是从System.Windows.UIElement, System.Windows.FrameworkElement, System.Windows.FrameworkContentElement和System.Windows.ContentElement继承。没 有任何的XAML元素与.NET CLR的抽象类对应。但是很多元素都有一个抽象类的派生类对应。

    通常有如下四种通用的XAML元素:
    Root元素:Windows和Page是最常用的根元素。这些元素位于XAML文件的根元素,并包含其他元素。
    Panel元素:帮助布置UI位置。常用的是StackPanel, DockPanel, Grid和Canvas。
    Control元素:定义XAML文件的控件类型。允许添加控件并自定义。
    Document元素:帮助实现文档提交。主要分为Inline和Block元素组,帮助设计的外观类似文档。一些有名的Inline元素有Bold, LineBreak, Italic。Block元素有Paragraph, List, Block, Figure和Table。

    XAML元素的属性与.NET类对象的属性类似,XAML的面向对象特征使得它的行为与之前的HTML类似。每个属性(实际上是类属性)继承了父元素的属性或者重载(如果重新设置了属性)。
为你的.NET语言增加WPF支持
【IT168技术资讯】微软发布了Visual Studio 2008 SDK预览版的一个新版本。在附带的例子中有一个是关于为.NET语言增加WPF支持的。
    Visual Studio 2008会包括一个Shell版本。这个版本可以免费使用和发行,并允许语言设计师为自己的编程语言提高Visual Studio IDE的能力。
    虽然多数语言开发人员不需要使自己的语言和.NET相适应,但要这么做还是有很多可观的好处的。除了能够访问.NET框架,语言设计师还可以深入到像 WPF设计器这样的.NET功能内部。其他的一些可以深入的地方包括WCF(Windows Communication Foundation)的一些功能,比如服务的引用和发现、数据设计器扩展等。
    许多Visual Studio功能需要一个表达式运算器。这个SDK的版本包括了一个基于IronPython的例子。作为微软在CLR中支持动态语言尝试的IronPython,也被作为WPF的例子使用。
    原文地址:http://www.infoq.com/cn/news/2007/08/WPF-Support
WPF使用流文档灵活地显示内容
【IT168 技术文档】
    Windows® Presentation Foundation (WPF) 提供了一系列功能。事实上,功能是如此之多,以至于其中一些非常重要的功能都没有得到应有的关注。一个最好的例子就是“流文档”功能,它可让开发人员在 WPF 中本机创建文档。“流文档”针对屏幕显示以及提供更动态和可以论证的更复杂模型。“流文档”几乎适用于与文本内容相关的所有方面,从产品说明到整本书籍。

    文本显示无疑是更重要的 UI 功能之一。在 WPF 界面中,您通常使用标签等控件来显示文本。但是在许多情形下,您需要的不只是简单地显示几个单词。流文档提供了一种更高级的方法,而它们实质上非常简单。 它们通过类似 HTML 文档的格式定义文本流,但其功能更强大,并可提供明显更先进的布局选项。

    通常使用基于 XML 的标准标记语言——可扩展应用程序标记语言 (XAML) 来定义“流文档”。XAML 对于流文档特别直观,主要是因为它与 HTML 类似。以下流文档示例创建了一段文字,并只对其中几个单词应用了粗体格式:
<FlowDocument
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Paragraph>The quick <Bold>brown fox</Bold> jumps over the lazy dog.
</Paragraph>
</FlowDocument> 
 
    可以看到,与 HTML 的相似性比在其他 XAML UI 中更明显。真正的元素名称是不同的,但是至少对简单的文档来说,模式非常相似。流文档一般以包含多个块的 FlowDocument 根元素开头。“块”是指流内的元素,通常是如上例所示的文本段落(当然还有其他块类型)。段落又可以包含其他元素,例如本例中的两个粗体单词。请注意,对 于任何其他 XAML 文档,根元素必须包含 XAML 特定的命名空间定义,否则无法被识别。这是 XAML 特定的实现细节,与流文档无关。请注意,命名空间定义只在独立的流文档中才需要。(流文档可以是更大的 XAML UI 的一部分,在这种情况下,该 UI 的根元素中会包含命名空间定义。)
    当然,用户永远不会看到流文档的 XAML(而 HTML 源则可在浏览器中查看),这与他们无法看到任何其他 UI 元素的 XAML 一样。相反,用户看到的是文档的最终呈现。对于这个特定的示例,您可通过多种方式看到结果。或许最简单的方式是将其键入 Windows SDK 附带的实用工具 XamlPad 中(请参见图 1)。


图 1 XamlPad 中显示的极其简单的流文档 (单击该图像获得较大视图)
    当然,这是一个非常简单的例子,文档的定义和嵌入式布局会复杂得多。流文档支持您能想到的所有格式,例如斜体、下划线、字体颜色和字体等。图2显示的是一个稍微高级的示例,其结果可在图 3 中看到。

图2 更多格式和项目符号列表
<FlowDocument
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Paragraph FontFamily=”Calibri” FontWeight=”Bold” FontSize=”24”>
WPF Flow Documents</Paragraph>
<Paragraph>WPF Flow Documents are <Italic>quite sophisticated</Italic>.
They support all common layout options, as well as many you probably
do <Span Foreground=”Red”>not expect</Span>.</Paragraph>
<List>
<ListItem><Paragraph>First List Item</Paragraph></ListItem>
<ListItem><Paragraph>Second List Item</Paragraph></ListItem>
<ListItem><Paragraph>Third List Item</Paragraph></ListItem>
</List>
<Paragraph>Of course, Flow Documents also support the definition
of <Bold><Span FontFamily=”Comic Sans MS” FontSize=”24”
Foreground=”Blue”>in line</Span></Bold> font sizes and font faces.
</Paragraph>
</FlowDocument>3 带有稍微高级格式的流文档 (单击该图像获得较大视图)
    本示例显示的是带有内嵌格式的若干段落。它还提供另一类型块元素的第一个示例,即列表,毫无疑问,它包含多个列表项。请注意,每个列表项反过来也只是包含 更多块元素的容器。因此,我不是简单地将文本置于一个列表项中,而是向每个列表项中添加一个段落元素。就此而论,我应该已向每个列表项或任何其他块类型添 加了多个段落。这可以让您在列表的单个列表项内创建高级布局,这在 HTML 等格式中一般行不通,因为此类格式只会让简单的文本字符串流向每个列表元素。
流文档基础知识

    至此您已了解了一些流文档的基础知识,接下来让我们回顾一下某些基础知识。如您所见,流文档是块的集合。在内部,所有块都是从 System.Windows.Documents.Block 类派生而来的 WPF 类。块又是从 ContentElement 派生而来(沿此链向上追寻几步),ContentElement 是 WPF 中专门为文档定义优化的一个相当低级别的类。此方法有些类似于您用来定义 WPF 界面的控件,它们都从 UIElement 派生而来。两者的继承树在概念上很相似,但并不完全相同。这意味着 WPF 控件和块不能直接组合。例如,一个按钮的标题不能设为一段文本,一个段落也不能直接包含一个按钮。这些控件和块之间存在一些细微差别,这是由于内容控件内 的布局和块内的布局的运作方式截然不同这一事实所致。幸运的是,这两类 WPF 元素之间需要弥合的差异非常小。就按钮而言,它可以包含由带格式的文本构成的 TextBlock 对象;而块可以通过特殊 BlockUIContainer 块类包含任何 WPF 控件。这意味着,流文档可以包含所有类型的 WPF 元素(包括交互式用户界面、媒体和三维元素),而从另一个角度看,流文档也可是任何 WPF 用户界面的一部分,例如可以是控件内容的一个高级布局元素,也可以是一个真正的元素,例如销售点应用程序中的某一项的描述。

    可用块的列表理论上是可扩充的,因为开发人员可以派生他们自己的块类,然后创建他们自己的针对文档呈现引擎的增强功能。这提供了我所了解的任何其他文档呈 现引擎都无法提供的自由度。但是,对一般的文档创建者公开的块数量通常有限。图4显示了最重要的块类型的列表。

图4 重要块类型
块 说明
段落 包含(潜在格式丰富的)文本
列表 包含各种不同类型(编号、项目符号等)的列表
表 包含与 Microsoft Word 或 HTML 中的表类似的表
BlockUIContainer 包含作为整个流的一部分的各种 UI 元素
段 包含一组其他块。段对于将常见属性应用于一组块中很方便,例如将同一字体属性应用于多个段落

    当使用 XAML 创建 WPF 流文档时,您事实上只要实例化某些类型。请看下面的 XAML 代码段(从此处起,我将省略命名空间定义,以让示例尽量简单):
<FlowDocument>
<Paragraph>Hello World!</Paragraph>
</FlowDocument> 
 
    这会实例化一个 FlowDocument 类和 Paragraph 类(其文本设为“Hello World!”)。该段落被添加到 FlowDocument 的块集合中。请注意,对于所有 XAML 而言,元素名称都区分大小写,并且精确映射到作为 WPF 一部分而提供的类。您也可通过编程方式创建相同文档,如下所示:
FlowDocument doc = new FlowDocument();
Paragraph para = new Paragraph();
para.Inlines.Add(“Hello World!”);
doc.Blocks.Add(para);
 
    当然,这远不及 XAML 提供的声明性方法那么直观,因此编程的方法只在特殊情形下采用。(当我需要创建一个格式丰富的报告,结果要更像一份真实的文档,而非通过许多报告引擎创建的表格形式的输出时,有时会使用此方法。)

    在许多情形下,段落本身带有格式丰富的内容,这也是通过实例化类实现的,如下所示:
<Paragraph>Hello <Bold>World!</Bold></Paragraph> 
 
    在本例中,该段落包含两个文本段——“Hello”(使用默认格式)和“World!”(粗体)。这比较有趣,因为这表示此 XAML 不只是实例化一个段落,并将其文本设为一个简单的字符串;相反,它创建了含有两个子段的一个段落,每个子段包含不同格式的文本。在 WPF 中,这些段称为内嵌元素。就如一个流文档可以包含多个不同类型的块一样,段落也可以包含各种类型的内嵌元素。内嵌元素有多种变体。有些内嵌元素就是所谓的 Span,它们代表应用了特定格式选项的文本段。此例中的 Bold 元素是 Span 的一个特殊情形,其默认字体粗细设为粗体。内嵌元素的另一种类型是 Run,它是带有默认格式的文本段。因此,上面的 XAML 其实只是下例的简化:
<Paragraph>
<Run>Hello </Run>
<Bold>World!</Bold>
</Paragraph> 
 
    当然,它要方便得多,您不必使用 XAML 定义每个内嵌元素,但是如果您要以编程方式创建相同示例,了解内嵌元素的概念就非常重要了,因为它们不可以在代码中省略。以下是前面两个 XAML 示例的对等代码段:
Paragraph para = new Paragraph();
para.Inlines.Add(new Run(“Hello “));
Bold b = new Bold();
b.Inlines.Add(“World!”);
para.Inlines.Add(b);
 
    Bold 是 Span 的特殊版本,其默认字体粗细设为粗体;Bold 类型由 Span 子类化而来,并且会覆盖 FontWeight 属性。类似特殊的 Span 还有 Italic 和 Underline。不过,这些特殊的 Span 并不是必不可少的,因为您也可以使用默认的 Span,并设置相应属性:
<Paragraph>Hello <Span FontWeight=”Bold”>World!</Span></Paragraph>
 
    当然,通过将某一文本段包到粗体或斜体标记中,来直接指定诸如粗体和斜体等属性的功能非常方便和直观,因此通常更多的是使用 <Bold>,而不是 <Span FontWeight="Bold">。不过,<Span> 元素还是非常有用的,因为有许多属性都要设为粗体以外的属性,而且那其中的大多数格式选项都没有单独的 Span 类型。事实上,许多非常常见的格式选项没有特殊的 Span。一个典型的示例就是设置字体。与 HTML 不同,流文档没有 <Font> 元素。相反,字体按如下方式设置:
<Paragraph>Hello <Span FontFamily=”Comic Sans MS” FontSize=”24”>
World!</Span></Paragraph> 
 
    诸如 FontFamily 等许多属性都可以始终在所有流文档类中找到。例如,若要设置一个完整段落而非只是一个内嵌元素的字体,您不使用 Span 即可做到:
<Paragraph FontFamily=”Comic Sans MS” FontSize=”24”>Hello World!</Paragraph> 
 
    还有 Span 和 Run 之外的一些内嵌元素。下面就是其他一些更有趣的内嵌元素:

    Figure Figure 是有些不寻常的内嵌元素,因为它们包含块。因此,从某种意义上讲,Figure 几乎就像流文档内的迷你流文档。Figure 经常用于高级布局功能,例如段落中被普通文本流包围的图像。 
    Floater Floater 是轻型的图形。它们不支持任何图形放置选项,但是如果您需要的只是除标准段落对齐之外还能做些简单对齐的功能,Floater 会比较有用。
    LineBreak LineBreak 元素的作用与其名称所指的意义完全相同:它们会在段落内引入换行符。
    InlineUIContainer InlineUIContainer 是 BlockUIContainer 的内嵌元素等同项。如果您需要将任何类型的 WPF 控件与您其他的内嵌元素组合使用(例如让一个按钮在一个段落文本内移动),InlineUIContainer 正是您所需要的。
    Figure 始终用于流文档中(LineBreak 也是如此,不过它们几乎不需要详细讨论)。以下示例使用一个图形,将一个图像显示为一个更大流文档的一部分:
<Paragraph>
<Figure Width=”200”>
<BlockUIContainer>
<Image Source=”Pictures\Humpback Whale.jpg” />
</BlockUIContainer>
<Paragraph Foreground=”Blue” FontFamily=”Consolas”>
The Whale</Paragraph>
</Figure>
The quick brown fox jumps over the lazy dog. The quick brown...
</Paragraph> 
 
请注意,WPF 流文档中没有 Image 块。相反,图像以标准的 WPF Image 控件内嵌为 BlockUIContainer。(相同的方法也用于流文档内诸如视频或交互式三维模型等内容)。图 5 显示了与此类似的一个文档的呈现。




图 5 文本环绕图片和标题 (单击该图像获得较大视图)
查看流文档

    现在,您已了解如何创建一些简单的流文档以及如何在 XamlPad 中查看它们。而目前我所忽略的是该如何在自然状态下查看流文档。毕竟,您不会期望用户打开 XamlPad,然后粘贴文档的 XAML。查看 XAML 流文档的一种方法是将其另存为一个扩展名为 .xaml 的文件,然后在 Windows 资源管理器中双击它。这会启动与 XAML 文件相关联的默认应用程序(通常是 Internet Explorer®),从而显示该文档。结果如图6所示。




图 6 在 Internet Explorer 中显示的 XAML 流文档 (单击该图像获得较大视图)
    Internet Explorer(及其他浏览器)可以显示 XAML 内容这一事实特别有趣,因为这是将流文档作为您的 Web 应用程序一部分显示的一张票证。换句话说,如果您将 XAML 流文档上传到您的 Web 服务器,而有人浏览到了该文件,他就会看到类似于图6的效果(假设该用户已安装 Microsoft® .NET Framework 3.0)。当然,这也是动态运作的。如果您的 ASP.NET Web 应用程序(或任何其他服务器端技术)动态生成了一个 XAML 流文档,并将其作为输出返回(假设内容类型已适当设为“application/xaml+xml”),用户就会看到作为您应用程序一部分的流文档,这在 许多情形下必然相当有用。图 7 显示了一个简单的生成流文档的 ASP.NET 页面。

图7 动态 ASP.NET 流文档
<%@ Page Language=”C#” ContentType=”application/xaml+xml” %>
<FlowDocument
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<Paragraph FontFamily=”Calibri” FontWeight=”Bold” FontSize=”24”>
WPF Flow Documents
</Paragraph>
<Paragraph FontFamily=”Calibri” FontWeight=”Bold” FontSize=”12”>
<%= DateTime.Now.ToString(“d”) %>
</Paragraph>
<Paragraph>
The quick brown fox jumps over the lazy dog. ...
</Paragraph>
</FlowDocument> 
 
显示流文档

    您可能已经注意到,每当显示流文档时(无论是在浏览器中还是在 XamlPad 中),显示的似乎不只是文档本身,还会显示其他少量内容。特别是,文档底部会呈现一些控件。如图 8 所示,流文档默认会通过 FlowDocumentReader 控件呈现,它提供了一组标准功能,例如缩放、分页、不同视图模式切换,甚至查找功能。事实上,流文档需要由一些能够显示它们的某类控件承载。流文档的默认 查看器是 FlowDocumentReader 控件,除非您明确使用其他控件,否则该控件会自动实例化。WPF 目前提供三个不同的控件用于查看流文档:




图 8 FlowDocumentReader 控件中的控件按钮 (单击该图像获得较大视图)
    FlowDocumentScrollViewer 此控件使用一个滚动条以连续的流显示文档,类似网页或 Microsoft Word 中的“Web 版式”。图 9 显示的是滚动查看器中的文档。




图 9 使用 FlowDocumentScrollViewer 控件 (单击该图像获得较大视图)
    FlowDocumentPageViewer 此控件以单独的页面显示流文档,让页面翻转而非滚动。这与 Word 中的“阅读版式”类似。图 10 显示的是页面查看器。在这里,图 9 中的文档使用 FlowDocumentPageViewer 控件呈现,滚动条被分页机制取代。这种简单的流布局方法已被一种更高级、多列的分页布局所取代。



图 10 使用 FlowDocumentPageViewer 控件 (单击该图像获得较大视图)
    FlowDocumentReader 此控件组合了滚动查看器和页面查看器,让用户可以在两种方法之间切换。这是用于流文档的默认控件,而且对于以显示复杂文本为特色的应用程序通常是一个不错 的选择。在图 11 中,图 9 和图 10 中显示的同一文档通过 FlowDocumentReader 呈现,它将滚动查看器和页面查看器两种方法结合在一起。此外,它还启用了其他控件中默认隐藏的搜索功能(其他查看器的确支持查找功能,通过执行 ApplicationCommands.Find 命令或从键盘上按 Ctrl+F 可实现该功能)。读取器控件还支持多页视图,这稍微改变了基于页面的呈现,以及列和图的呈现方式。



图 11 使用 FlowDocumentReader 控件 (单击该图像获得较大视图)
    虽然 FlowDocumentReader 几乎对所有基本使用情形都很有吸引力,但选择怎样的控件还需视您的情况而定。它用途广泛且功能强大,并支持分页布局,这在许多情形下是比滚动更高级的功 能。关于该主题的更详细讨论不在本文探讨范围之内,但事实证明,滚动及重合等相关效果是人们较之数字化文本更喜欢打印文本的主要原因之一。分页方法在许多 情况下更为自然,有助于让数字化阅读被更普遍接受。

    那么您如何定义要使用哪个控件呢?一个简单但相当强力的方法是将想要的控件添加到文档的 XAML 中:
<FlowDocumentScrollViewer
xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<FlowDocument>
<Paragraph>The quick <Bold>brown fox</Bold> jumps over the lazy
dog.</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer> 
 
    在本例中,文档根已被设为一个 FlowDocumentScrollViewer 标记。也就是说,您不再只是定义一个单纯的文档而已。相反,您在定义一个完整的 XAML 界面,而它碰巧使用滚动查看器作为其根。滚动查看器的内容是最开始示例中的流文档。(请注意,命名空间定义现在使用滚动查看器标记,而非流文档标记)。图 9 到图 11 是使用此方法创建的,不同的查看器控件用作根元素。

    我为何把这称为强力方法呢?这是因为,从结构角度看,将用户界面定义与其实际数据相混合会导致一些问题。而更理想的状况是将文档与其界面分开。将读取器与 文档混合在一起有点像创建一个 SQL Server™ 表,并出于某种原因定义该表只能在 Windows Forms DataGrid 中显示。有若干方法可让文档与 UI 定义分离。如果想使用上文所示的 ASP.NET 方法将流文档作为 Web 应用程序的一部分显示,您可使用所需的查看器控件定义 ASP.NET 页面,然后只要使用标准 ASP.NET 代码合并到实际内容(单独存储,可能在数据库中)即可。

    另一方面,在一个典型的 WPF 应用程序中,您可以只要使用标准 WPF、Windows 和 XAML 浏览器应用程序 (XBAP) 方法来定义您的用户界面,然后动态加载您的文档即可。图 12 显示的是使用我文章中的一个虚构库的一个简单示例,这些文章显示在左上角的一个列表框中。用户从列表中选择一篇文章时,该文档会自动加载到占用大部分窗体 的 Flow Document Reader 控件。请注意,诸如 alpha 值混合处理等标准 WPF 技术在此设置中也能使用。您会注意到,实际的流文档是半透明的,背景中我的照片也在闪烁。另外也请注意,应用程序使用了一个列表框、图像,一个标签和一个 FlowDocumentReader 控件来创建虚构文章的库。




    图 12 使用列表框、图像、标签和 FlowDocumentReader 控件 (单击该图像获得较大视图)
这个例子最棘手的地方是将实际文档加载到查看器控件中。这通过 System.Windows.Markup.XamlReader 类实现,它允许动态加载任何 XAML 内容,包括但不限于流文档。以下是我绑定到列表框选定更改事件的一行代码:
documentReader.Document =
(FlowDocument)XamlReader.Load(
File.OpenRead(fileName));
 
    Load 方法会返回一个对象,因为 XAML 文件中的根元素可以代表许多不同类型。在我的例子中,我知道返回值为 FlowDocument,因此我只要执行一个转换,并将该文档指定给 FlowDocumentReader 控件的 Document 属性即可(此例中,我将控件实例命名为 documentReader)。请记住,这只是个例子。生产品质的代码此处当然还需要一些错误处理。

    请注意,您了解的关于 WPF 的所有东西都适用于本例。例如,读取器控件只是支持样式的标准 WPF 控件。也就是说,您可以完全更改所有 UI 元素的外观,例如缩放栏、视图模式切换或分页控件。(您的控制能力受到限制的唯一元素是搜索框,虽然如果您不喜欢它,就根本不必用它。)

    此外,我的例子显示的是基于 Windows 的应用程序,相同的应用程序也可以作为 XBAP 部署,并在 Web 浏览器内运行(当然,我们还是假设用户已安装了 .NET Framework 3.0)。请注意,Microsoft Silverlight™(原代号为“WPF/E”)是不够的,因为 Silverlight 只支持 WPF 的子集,且并不支持流文档。
创建流文档 

    如何编写流文档?当然,开发人员始终可以使用诸如 XamlPad 等低级工具来编写流文档。但是,在现实环境下,这不大可能。通常,流文档是使用 WYSIWYG 编辑器或通过从现有文档格式进行的内容转换来创建的。由于流文档可以使用 XAML 定义,因此转换现有 XML 内容特别简单。但也可以转换 HTML 和 Word 文档,而无需付出过大的精力(尽管需要编码,因为迄今为止尚未出现现成工具)。

    对于 WYSIWYG 编辑,WPF 提供了一个现成的控件。WPF RichTextBox 控件可以本机编辑 XAML 流文档。该控件名称让人误以为它是专门针对 RTF 格式。尽管这个控件也支持 RTF,但实际上它主要用于流文档。事实上,该控件实际上会反映流文档查看控件,只不过它也支持编辑。有些人甚至会说,RichTextBox 控件应该被视为显示流文档的另一种方式。

    将下列示例键入 XamlPad 中,以查看运行中的 RichTextBox 控件:
<RichTextBox
xmlns=’http://schemas.microsoft.com/
winfx/2006/xaml/presentation’
xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
<FlowDocument>
<Paragraph>
The quick brown fox jumps over the lazy dog.
</Paragraph>
</FlowDocument>
</RichTextBox> 
 
    恰如读取器控件一样,RichTextBox 也有一个 Document 属性,您可以自动以此会话中的流文档填充其值。这实际上会创建一个与 FlowDocumentScrollViewer 控件看起来很相似的 UI,只不过其中的文本可以编辑。请注意,此文本框控件始终以滚动方式处理流文档。在分页或多列模式下,无法在 RichTextBox 中编辑流文档。不过,编辑操作的结果是一个标准流文档,该文档可以使用您已看到的任何一种查看器机制显示,其中包括多列和分页模式。

    关于 RichTextBox,值得一提的其中一项功能是集成的拼写检查。您可以按如下所示启用该功能: 
<RichTextBox SpellCheck.IsEnabled=”true”>
<FlowDocument>...</FlowDocument>
</RichTextBox>13 显示了运行中的拼写检查程序。



    图 13 带有拼写检查功能的 RichTextBox 控件 (单击该图像获得较大视图)
使 用此控件唯一复杂的地方是加载与保存。在许多情形下,您可能不会像在之前的例子中那样,将 RichTextBox 内容编码到 UI XAML 中,而是要动态加载和保存文档。RichTextBox 中文本的加载操作与为查看器控件加载流文档相同(见上文)。保存文档本质上则完全相反:您要先拿到文档对象,然后将其序列化回 XAML,如下所示:
System.Windows.Markup.XamlWriter.
Save(richTextBox.Document)
 
    这会将 XAML 作为一个字符串返回,然后您可以将其存储到文件或数据库中,或者使用您能想到的任何其他方式。

    RichTextBox 非常方便,不过在这里还是要提醒几句话。虽然流文档代表了可用于呈现屏幕文档的最复杂的技术,但 RichTextBox 控件却一点也不复杂。它是编辑小型文档和文本段的极佳选择,但是您不会用它来编写书籍、杂志或营销手册。对于这些长格式,它的呈现过于简单,因为它不支持 除滚动布局之外的其他任何布局(也就是说,还没有一种很好的可视方式可用于创建我稍后将谈到的高级布局)。同样,用于保存文档的方法也经常不尽人意。 XmlWriter 类只是使用实时的内存中文档,并将其转换为 XAML,但遗憾的是,对于大规模的流文档操作非常重要的许多概念(例如样式),它并未注意。结果,尽管 XAML 忠实地保存了文档的外观,但文档看起来往往不太清爽,并且很大。RichTextBox 控件当然还是很有用的,但是别指望将它作为屏幕内容的桌面出版解决方案(虽然这类应用程序非常急需)。

    探究布局可能性

    至此您已了解了如何编写和查看流文档,接着让我们回到文档本身,看看更多的功能。流文档非常复杂,探究所有可用功能超出了本文范围,不过我想再讨论几项功能。

    其中一项一直让我着迷的功能是“最佳段落”。启用该功能后,可以在指定段落内尽可能平均地分布空白,从而带来显著改进的阅读体验。“最佳段落”特别适合与 另一项内置功能“断字”搭配使用,该功能(居然)会执行动态整个流文档或者个别段落的断字。

    启用最佳段落和断字功能是项非常简单的操作:
<FlowDocument IsOptimalParagraphEnabled=”true”
IsHyphenationEnabled=”true”>14 显示的是相同的文档,只是呈现时启用或禁用了这些功能。两个版本间的区别非常细微,但是非常重要。请注意,左边的版本看起来更平和,主要因为词与词之间的 空白分布得更平均,且从整体上减少了。特别是在屏幕上阅读大量文本时,这个看起来细小的区别会变得极为重要。



图 14 最佳段落和断字 (单击该图像获得较大视图)
    如您所见,FlowDocumentReader 控件采取多列的方法呈现文本。这是另一项非常重要的可读性功能,因为人们不喜欢读跨越整个宽屏显示页面宽度的一行行文字。实际列宽因各种因素会有所不同, 例如用于内容显示的可用总宽度、缩放系数和定义的列宽等。流文档的默认列宽为字体大小的 20 倍,默认字体大小约为 300 个与设备无关的像素(3 1/8 英寸的精确尺寸显示)。您可以很轻松地覆盖此默认设置:
<FlowDocument ColumnWidth=”400”>
 
    这会产生宽度约 400 像素的列。不过,还有其他一些因素会影响实际宽度。举例来说,如果缩放比例是 50%,那么实际列宽就只有 200 像素。另外,到目前为止,列宽更多地会被看作最小列宽。这意味着,如果可用总宽度为 900 像素,要呈现结果包含两列,并且要充分填满这整个 900 像素的话,就要让每列的宽度都超过定义的 400 像素。通常都需要这样,因为它会让呈现结果看起来非常美观。不过,如果您不想执行该行为,而只希望列宽实际就是 400 像素的话,可以确保列宽不是灵活可变的:
<FlowDocument ColumnWidth=”400” IsColumnWidthFlexible=”false”>
 
    现在,所有列都正好是 400 像素(100% 缩放),剩余空间就让它显示为空白。

    另一个您可能想尝试的与列相关的设置是列之间的空隙。这可以通过 ColumnGap 属性调整(此设置也是基于与设备无关的像素数):
<FlowDocument ColumnGap=”25”>
 
其中一个相关的设置是列规则,它允许在列之间定义一个可视元素。请看此例(其结果见图 15):




图 15 列之间采用简单规则的流文档 (单击该图像获得较大视图)
<FlowDocument ColumnRuleWidth=”5” ColumnRuleBrush=”Red”>
 
    当然,在许多出版物中,文档并不只是采用简单的列布局。通常还存在从一般流中提取出来的其他元素。您已见过这样的例子,例如将图像置于文档中。图 12 显示了图形设计师常用的一种排列方式。此图像位于两列之间,周围环绕文字,图像方方正正地位于内容中间,并没有影响任何一列的文字布局。这是一种常见的布 局选择,只是还不能用于流文档之前我所了解的动态屏幕阅读环境。
   
    创建此类布局的关键是图形块,它允许定义不与文档其余部分那样布局的内容。将图像置于图形标记内部就是一例,但图形还有许多其他用途。例如,您可以使用图形来定义横跨整个文档宽度的标题:
<Paragraph>
<Figure HorizontalAnchor=”ContentLeft” VerticalAnchor=”ContentTop”
Width=”1Content”>
<Paragraph FontSize=”36” FontWeight=”Bold”>Go With
The Flow</Paragraph>
</Figure>
Windows Presentation Foundation in Windows Vista provides a great set
of features.
</Paragraph> 
 
    在本代码中,图形包含另一个段落,即用作标题的文本。请注意,这里有一些您可用来创建高级、灵活文档的便捷属性。例如,看一下图形的宽度。我没有将宽度设 为特定像素数,而是将其设为内容的确切宽度,这会根据整个内容的宽度自动调整图形宽度。

    请看图 16。其中,您会注意到标题(通过图形放置)设为横跨整个内容宽度,这就将所有四列的位置都向下推移了。该图像从垂直和水平方向看都定位于页面中央。




图 16 标题横跨四列 (单击该图像获得较大视图)
    请注意,其宽度与内容相关的图形不必始终与内容一样宽。以下例来说,图形宽度设为内容宽度的 75%<Figure Width=”0.75Content”>
 
    宽度也可与其他项相关,例如列宽。下例图形始终是两列宽(除非只显示一列,那样宽度就会减为一列):
<Figure Width=”2Column”>
 
    当然,图形高度可通过类似方式定义(虽然图形通常是随着内容纵向变化)。

    另一重要方面是图形的位置。在代码段中,它设为横向定位为靠左,纵向定位为靠上。也就是说,图形会出现在当前内容页面的左上角,而无论其实际如何定义。然 而在本示例中,图形被定义为文档的第一个元素,但即使该标题之前已有段落,它也会由于这些设置而被上移和左移。图 12 和图 16 中的照片已按类似方式,将其横向定位为“PageCenter”,在列之间移动。(所有这些设置的可用属性值都可以在 WPF 文档中找到)。

    您可能已经注意到,本文涉及了大量手动编码。例如,每当需要改变字体时,您都要将该信息添加到块或内嵌元素中。到目前为止,这还不是一个大问题,因为大部 分示例都很小。但是,如果有一本每 50 页为一章的书,您要改变每一段的字体,每次都手动来改的话,无疑会很繁重。幸运的是,现在有了一个更好的办法:如 WPF 中的其他任何内容一样,流文档支持样式。样式可被定义为实际流文档中指定名称的资源。以下是定义字体信息的样式:
<FlowDocument>
<FlowDocument.Resources>
<Style x:Key=”MyStyle”>
<Setter Property=”TextElement.FontSize” Value=”12” />
<Setter Property=”TextElement.FontFamily” Value=”Bodoni MT” />
</Style>
<FlowDocument.Resources>
...
</FlowDocument> 
 
    然后,该样式会通过以下方式应用到段落(和其他元素):
<Paragraph Style=”{StaticResource MyStyle}”>The quick... </Paragraph> 
 
    由于流文档的特性,样式特别常用。建议您对于最简单情形之外的任何情形,都使用样式来定义大部分格式选项,而不是通过个别内嵌元素的属性。样式可让您的文档保持紧凑,而且更易维护。
View Code

WPF入门基础知识

 WPF基础知识 
Windows Presentation Foundation (WPF) 是下一代显示系统,用于生成能带给用户震撼视觉体验的 
Windows 客户端应用程序。使用 WPF,您可以创建广泛的独立应用程序以及浏览器承载的应用程
序。 
WPF 的核心是一个与分辨率无关并且基于向量的呈现引擎,旨在利用现代图形硬件的优势。WPF 
通过一整套应用程序开发功能扩展了这个核心,这些功能包括 可扩展应用程序标记语言 (XAML)、
控件、数据绑定、布局、二维和三维图形、动画、样式、模板、文档、媒体、文本和版式。WPF 
包含在 Microsoft .NET Framework 中,使您能够生成融入了 .NET Framework 类库的其他元素的应用
程序。 
为了支持某些更强大的 WPF 功能并简化编程体验,WPF 包括了更多编程构造,这些编程构造增强
了属性和事件:依赖项属性和路由事件。有关依赖项属性的更多信息,请参见依赖项属性概述。
有关路由事件的更多信息,请参见路由事件概述。 
这种外观和行为的分离具有以下优点: 
1降低了开发和维护成本,因为外观特定的标记并没有与行为特定的代码紧密耦合。 
2开发效率更高,因为设计人员可以在开发人员实现应用程序行为的同时实现应用程序的外观。 
3可以使用多种设计工具实现和共享 XAML 标记,以满足应用程序开发参与者的要求:Microsoft 
Expression Blend 提供了适合设计人员的体验,而 Visual Studio 2005 针对开发人员。 
4 WPF 应用程序的全球化和本地化大大简化(请参见 WPF 全球化和本地化概述)。 

在运行时,WPF 将标记中定义的元素和属性转换为 WPF 类的实例。例如,Window 元素被转换为 Window 类的实
例,该类的 Title 属性 (Property) 是 Title 属性 (Attribute) 的值。 
注意在constructor中Call: InitializeComponent(); 
x:Class 属性用于将标记与代码隐藏类相关联。InitializeComponent 是从代码隐藏类的构造函数中调用的,用于将标记中定义的 
UI 与代码隐藏类相合并。((生成应用程序时将为您生成 InitializeComponent,因此您不需要手动实现它。)x:Class 和 
InitializeComponent 的组合确保您的实现无论何时创建都能得到正确的初始化。 

.NET Framework、System.Windows、标记和代码隐藏构成了 WPF 应用程序开发体验的基础 

窗口: WPF 对话框:MessageBox、OpenFileDialog、SaveFileDialog 和 PrintDialog。 
WPF 提供了以下两个选项作为替代导航宿主: 

. Frame,用于承载页面或窗口中可导航内容的孤岛。 
. NavigationWindow,用于承载整个窗口中的可导航内容。 


启动:StartupUri="MainWindow.xaml" />此标记是独立应用程序的应用程序定义,并指示 WPF 创建一个在应用程
序启动时自动打开 MainWindow 的 Application 对象。 

WPF 控件一览 


此处列出了内置的 WPF 控件。 

. 按钮:Button 和 RepeatButton。 
. 对话框:OpenFileDialog、PrintDialog 和 SaveFileDialog。 
. 数字墨迹:InkCanvas 和 InkPresenter。 
. 文档:DocumentViewer、FlowDocumentPageViewer、FlowDocumentReader、FlowDocumentScrollViewer 
和 StickyNoteControl。 
. 输入:TextBox、RichTextBox 和 PasswordBox。 
. 布局:Border、BulletDecorator、Canvas、DockPanel、Expander、Grid、GridView、GridSplitter、
GroupBox、Panel、ResizeGrip、Separator、ScrollBar、ScrollViewer、StackPanel、Thumb、Viewbox、
VirtualizingStackPanel、Window 和 WrapPanel。 
. 媒体:Image、MediaElement 和 SoundPlayerAction。 
. 菜单:ContextMenu、Menu 和 ToolBar。 
. 导航:Frame、Hyperlink、Page、NavigationWindow 和 TabControl。 
. 选择:CheckBox、ComboBox、ListBox、TreeView、RadioButton 和 Slider。 
. 用户信息:AccessText、Label、Popup、ProgressBar、StatusBar、TextBlock 和 ToolTip。 


输入和命令 :控件通常检测和响应用户输入。WPF 输入系统使用直接事件和路由事件来支持文本输入、焦点管理和鼠标定位。有
关更多信息,请参见输入概述。 
布局系统的基础是相对定位,它提高了适应窗口和显示条件变化的能力。此外,布局系统还管理控件之间的协商以确定布局。协
商过程分为两步:第一步,控件向父控件通知它所需的位置和大小;第二步,父控件通知该控件它可以具有多大空间 

. Canvas:子控件提供其自己的布局。 
. DockPanel:子控件与面板的边缘对齐。 
. Grid:子控件按行和列放置。 
. StackPanel:子控件垂直或水平堆叠。 
. VirtualizingStackPanel:子控件被虚拟化,并沿水平或垂直方向排成一行。 
. WrapPanel:子控件按从左到右的顺序放置,如果当前行中的控件数多于该空间所允许的控件数,则换至下一行 


由父控件实现的、供子控件使用的属性是一种 WPF 构造,称为“附加属性” 

为了简化应用程序开发,WPF 提供了一个数据绑定引擎以自动执行这些步骤。数据绑定引擎的核心单元是 Binding 类,它
的任务是将控件(绑定目标)绑定到数据对象(绑定源)。下图说明了这种关系。 


基本数据绑定示意图 
WPF 数据绑定引擎还提供了其他支持,包括验证、排序、筛选和分组。此外,当标准 WPF 控件显示的 UI 不合适时,数据绑定
还支持使用数据模板为绑定的数据创建自定义 UI。 
WPF 引进了一组广泛的、可伸缩且灵活的图形功能,它们具有以下优点: 

. 与分辨率和设备无关的图形。WPF 图形系统的基本度量单位是与设备无关的像素,它等于一英寸的 1/96,而不管实际的
屏幕分辨率是多少,为与分辨率和设备无关的呈现提供了基础。每个与设备无关的像素都会自动缩放,以符合呈现该像素的
系统上的每英寸点数 (dpi) 设置。 
. 更高的精度。WPF 坐标系是使用双精度浮点数字测量的,而不是使用单精度浮点数字。转换值和不透明度值也以双精度表
示。WPF 还支持广泛的颜色域 (scRGB),并为管理来自不同颜色空间的输入提供完整的支持。 
. 高级图形和动画支持。WPF 通过为您管理动画场景简化了图形编程;您不需要担心场景处理、呈现循环和双线性内插算法。
此外,WPF 还提供了命中测试支持和全面的 alpha 合成支持。 
. 硬件加速。WPF 图形系统利用了图形硬件的优势来最小化 CPU 使用率。 


 


Path 对象可用于绘制闭合或开放形状、多线形状,甚至曲线形状。 

Geometry 对象可用于对二维图形数据进行剪裁、命中测试和呈现。 
WPF 二维功能的子集包括渐变、位图、绘图、视频绘制、旋转、缩放和扭曲等视觉效果。这些都可以使用画笔完成;下图演示了某
些示例。 
WPF 动画支持可以使控件变大、旋转、调节和淡化,以产生有趣的页面过渡和更多效果。您可以对大多数 WPF 类(甚至自定义类)
进行动画处理。下图演示了一个简单的活动动画。 
为了加快高质量的文本呈现,WPF 提供了以下功能: 

. OpenType 字体支持。 
. ClearType 增强。 
. 利用硬件加速优势的高性能。 
. 文本与媒体、图形和动画的集成。 
. 国际字体支持和回退机制。 



WPF 本身支持使用三种类型的文档:流文档、固定文档和 XML 纸张规范 (XPS) 文档。WPF 还提供了用于创建、查看、管理、批注、
打包和打印文档的服务。 
XML 纸张规范 (XPS) 文档建立在 WPF 的固定文档基础上。XPS 文档使用基于 XML 的架构进行描述,该架构本质上就是电子纸的
分页表示。XPS 是一个开放的、跨平台的文档格式,旨在简化分页文档的创建、共享、打印和存档。XPS 技术的重要功能包括: 

打包 

WPF System.IO.Packaging API 允许您的应用程序将数据、内容和资源组织成一个可移植、易于分发和访问的 ZIP 文档。可以包
括数字签名以对程序包中包含的项目进行身份验证,并确定签名的项目未被篡改或修改。您还可以使用权限管理对软件包进行加密,
以限制对受保护信息的访问。 

打印 

.NET Framework 包括一个打印子系统,WPF 通过支持更好的打印系统控制对其进行了增强。打印增强功能包括: 

. 实时安装远程打印服务器和队列。 
. 动态发现打印机功能。 
. 动态设置打印机选项。 
. 打印作业重新路由和重新排列优先级次序。 


内容模型 

大多数 WPF 控件的主要目的都是为了显示内容。在 WPF 中,构成控件内容的项目类型和数量被称为控件的“内容模型”。有些控件
只能包含一个项目和内容类型;例如,TextBox 的内容为字符串值,该值被分配给 Text 属性。 

触发器 

尽管 XAML 标记的主要目的是实现应用程序的外观,但您仍然可以使用 XAML 实现应用程序行为的某些方面。一个示例就是使用触
发器根据用户交互更改应用程序的外观。有关更多信息,请参见样式设置和模板化中的“触发器”。 

数据模板 

控件模板使您可以指定控件的外观,数据模板则允许您指定控件内容的外观。数据模板通常用于改进绑定数据的显示方式。下图演示 
ListBox 的默认外观,它被绑定到一个 Task 对象集合,该集合中的每个任务都有一个名称、说明和优先级。 

开发人员和设计人员使用样式可以对其产品的特定外观进行标准化。WPF 提供了一个强大的样式模型,其基础是 Style 元素。下面
的示例创建一个样式,该样式将窗口中的每个 Button 的背景色设置为 Orange。 

资源 

一个应用程序中的各控件应共享相同的外观,包括从字体和背景色到控件模板、数据模板和样式的所有方面。您可以使用 WPF 对 用
户界面 (UI) 资源的支持将这些资源封装到一个位置,以便于重复使用。 
资源范围有多种,包括下面按解析顺序列出的范围: 

1. 单个控件(使用继承的 FrameworkElement..::.Resources 属性)。 
2. Window 或 Page(也使用继承的 FrameworkElement..::.Resources 属性)。 
3. Application(使用 Application..::.Resources 属性)。 


范围的多样性使您可以灵活选择定义和共享资源的方式。 


 作为将资源与特定范围直接关联的一个备用方法,您可以使用单独的 ResourceDictionary(可以在应用程序的其他部分引用)
打包一个或多个资源。例如,下面的示例在资源字典中定义默认背景色。 
由于 WPF 的外观由模板定义,因此 WPF 为每个已知 Windows 主题包括了一个模板, 
WPF 中的主题和外观都可以使用资源字典非常轻松地进行定义 
自定义控件 
尽管 WPF 提供了大量自定义项支持,您仍然可能会遇到现有 WPF 控件不能满足应用程序或用户需求的情况。在以下情况下可能会
出现这种情形: 

. 无法通过自定义现有 WPF 实现的外观来创建您需要的 UI。 
. 现有 WPF 实现不支持(或很难支持)您需要的行为。 
. 用户控件模型。从 UserControl 派生的自定义控件,由其他一个或多个控件组成。 
. 控制模型。从 Control 派生的自定义控件,用于生成使用模板将其行为和外观相分离的实现,与多数 WPF 控件非常相似。
从 Control 派生使您可以比用户控件更自由地创建自定义 UI,但可能需要投入更多精力。 
. 框架元素模型。从 FrameworkElement 派生的自定义控件,其外观由自定义呈现逻辑(而不是模板)定义。 


 
WPF 是一种全面的显示技术,用于生成多种类型的具有视觉震撼力的客户端应用程序。本文介绍了 WPF 的关键功能。 
下一步为生成 WPF 应用程序! 
将数据连接到控件 

在此步骤中,您将编写代码来检索从 HomePage 上的人员列表中选定的当前项,并在实例化过程中将对该当前项的引用传递给 
ExpenseReportPage 的构造函数。ExpenseReportPage 使用已传入的项设置数据上下文,这就是 ExpenseReportPage.xaml 
中定义的控件要绑定的内容。 
WPF 3.5 定义了一个新的 XML 命名空间 http://schemas.microsoft.com/netfx/2007/xaml/presentation。在使用 WPF 
3.5 生成应用程序时,可以使用此命名空间或在 WPF 3.0 中定义的命名空间。 
应用程序 

应用程序模型已得到下列改进: 

. 提供全面的外接程序支持,可以支持独立应用程序和 XAML 浏览器应用程序 (XBAP) 中的非可视化和可视化外接程序。 
. XBAP 现在可在 Firefox 中运行。 
. 可以在 XBAP 与同一源站点中的 Web 应用程序之间共享 Cookie。 
. 为提高工作效率而改进的 XAML IntelliSense 体验。 
. 更广泛的本地化支持。 


WPF 中的可视化和非可视化外接程序 

可扩展的应用程序可以公开它的功能,从而允许其他应用程序与该应用程序集成并扩展其功能。外接程序是应用程序公
开其扩展性的一种常见方式。在 .NET Framework 中,外接程序通常是作为动态链接库 (.dll) 打包的程序集。外接程


http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/minus.gif
序由宿主应用程序在运行时动态加载,以便使用和扩展由宿主公开的服务。宿主和外接程序通过已知协定进行交互,该
协定通常是由宿主应用程序发布的公共接口。 


对 XBAP 的 Firefox 支持 

WPF 3.5 的一个插件使得 XBAP 能够从 Firefox 2.0 中运行,WPF 3.0 中没有这个功能。其中的重要功能包括: 

. 如果 Firefox 2.0 是默认浏览器,XBAP 将使用这一配置。也就是说,如果 Firefox 2.0 是默认浏览器,XBAP 将不使
用 Internet Explorer。 
. 运行 Internet Explorer 的 XBAP 所具备的安全功能对于在 Firefox 2.0 中运行的 XBAP 同样可用,其中包括部分信
任的安全沙盒。由浏览器提供的其他安全功能因浏览器而异。 


Cookie 

独立 WPF 应用程序和 XBAP 可以创建、获取和删除会话和持久性 Cookie。在 WPF 3.5 中,可以在 XBAP、Web 
服务器和同一源站点中的 HTML 文件之间共享持久性 Cookie。 
图形 

现在,您可以将通过 HTTP 下载的图像缓存到本地 Microsoft Internet Explorer 临时文件缓存中,这样,对该图像
的后续请求将来自本地磁盘而非 Internet。根据图像大小的不同,这一功能可以显著改善网络性能。为支持此功能,
添加了下面的成员: 

. BitmapImage..::.UriCachePolicy 
. BitmapDecoder..::.Create(Uri, BitmapCreateOptions, BitmapCacheOption, RequestCachePolicy) 
. BitmapFrame..::.Create(Uri, RequestCachePolicy) 
. BitmapFrame..::.Create(Uri, BitmapCreateOptions, BitmapCacheOption, RequestCachePolicy) 


添加了 BitmapSource..::.DecodeFailed 事件,用以在图像由于文件头损坏而加载失败时向用户发出通知。 
数据绑定 
数据绑定已得到下列改进: 

. 新的调试机制降低了调试数据绑定的难度。 
. 数据模型通过提供对 IDataErrorInfo 接口的支持,可以实现对业务层的验证。另外,验证模型现在还支持使用属性语法
来设置验证规则。 
. 数据绑定模型现在支持 LINQ 和 XLINQ。 


 文档 

FlowDocumentPageViewer、FlowDocumentScrollViewer 和 FlowDocumentReader 各有一个名为 Selection 的
新的公共属性。该属性获取表示文档中选定内容的 TextSelection。 
应用程序是否具有特定于语言或非特定于语言的资源。例如,您是否为 Application、Page 和 Resource 类型指定了 
UICulture 项目属性或可本地化的元数据? 

本主题包括下列各节。 


WPF 在 .NET Framework 中的位置。
. System.Object 
. System.Threading.DispatcherObject 
. System.Windows.DependencyObject 
. System.Windows.Media.Visual 
. System.Windows.UIElement 
. System.Windows.FrameworkElement 
. System.Windows.Controls.Control 
. 摘要 
. 相关主题 
. WPF 主要编程模型是通过托管代码公开的。在 WPF 的早期设计阶段,曾有过大量关于如何界定系统的托管组件和非托
管组件的争论。CLR 提供一系列的功能,可以令开发效率更高并且更加可靠(包括内存管理、错误处理和通用类型系统
等),但这是需要付出代价的。 
. 下图说明了 WPF 的主要组件。关系图的红色部分(PresentationFramework、PresentationCore 和 milcore)是 
WPF 的主要代码部分。在这些组件中,只有一个是非托管组件 – milcore。milcore 是以非托管代码编写的,目的是
实现与 DirectX 的紧密集成。WPF 中的所有显示是通过 DirectX 引擎完成的, 
. 
. System.Threading.DispatcherObject 



. WPF 中的大多数对象是从 DispatcherObject 派生的,这提供了用于处理并发和线程的基本构造。WPF 基于调度程序实
现的消息系统。其工作方式与常见的 Win32 消息泵非常类似;事实上,WPF 调度程序使用 User32 消息执行跨线程调用。 
. System.Windows.DependencyObject 
. 生成 WPF 时使用的主要体系结构原理之一是首选属性而不是方法或事件。属性是声明性的,使您更方便地指定意图而
不是操作。它还支持模型驱动或数据驱动的系统,以显示用户界面内容。这种理念的预期效果是创建您可以绑定到的更
多属性,从而更好地控制应用程序的行为。 


WPF 提供一个丰富的属性系统,该属性系统是从 DependencyObject 类型派生的。该属性系统实际是一个“依赖”属性系
统,因为它会跟踪属性表达式之间的依赖关系,并在依赖关系更改时自动重新验证属性值。例如,如果您具有一个会继承的
属性(如 FontSize),当继承该值的元素的父级发生属性更改时,会自动更新系统。 
WPF 属性系统的基础是属性表达式的概念。 
属性系统还提供属性值的稀疏存储 
属性系统的最后一个新功能是附加属性的概念 

Visual 实际上是到 WPF 组合系统的入口点 
可视对象和绘制指令的整个树都要进行缓存 
System.Windows.UIElement 

UIElement 定义核心子系统,包括 Layout、Input 和 Event。 
输入是作为内核模式设备驱动程序上的信号发出的,并通过涉及 Windows 内核和 User32 的复杂进程路由到正确的进程
和线程。与输入相对应的 User32 消息一旦路由到 WPF,它就会转换为 WPF 原始输入消息,并发送到调度程序。WPF 
允许原始输入事件转换为多个实际事件,允许在保证传递到位的情况下在较低的系统级别实现类似“MouseEnter”的功能。 
每个输入事件至少会转换为两个事件 – “预览”事件和实际事件。WPF 中的所有事件都具有通过元素树路由的概念。如
果事件从目标向上遍历树直到根,则被称为“冒泡”,如果从根开始向下遍历到目标,它们被称为“隧道”。输入预览事件
隧道,使树中的任何元素都有机会筛选事件或对事件采取操作。然后,常规(非预览)事件将从目标向上冒泡到根。 

为了进一步深化此功能,UIElement 还引入了 CommandBindings 的概念。WPF 命令系统允许开发人员以命令终结点(一种用于
实现 ICommand 的功能)的方式定义功能 

FrameworkElement 引入的主要策略是关于应用程序布局。FrameworkElement 在 UIElement 引入的基本布局协定之上生成,
并增加了布局“插槽”的概念,使布局制作者可以方便地拥有一组面向属性的一致的布局语义。HorizontalAlignment、
VerticalAlignment、MinWidth 和 Margin 等属性使得从 FrameworkElement 派生的所有组件在布局容器内具有一致的行为。 

FrameworkElement 引入的两个最关键的内容是数据绑定和样式。WPF 中数据绑定的最值得关注的功能之一是引入了数据模板 
样式实际上是轻量级的数据绑定 
System.Windows.Controls.Control 
控件的最重要的功能是模板化。 
数据模型(属性)、交互模型(命令和事件)及显示模型(模板)之间的划分,使用户可以对控件的外观和行为进行完全自定义。最
常见的控件数据模型是内容模型 


您就能够创建更丰富的应用程序,这些应用程序在根本上会将数据视为应用程序的核心驱动力。 
可扩展应用程序标记语言 (XAML) 语言支持,以便您能够在可扩展应用程序标记语言 (XAML) 标记中创建大部分应用程序 UI。 
XAML 简化了为 .NET Framework 编程模型创建 UI 的过程。您可以在声明性 XAML 标记中创建可见的 UI 元素,然后使用代码隐
藏文件(通过分部类定义与标记相连接)将 UI 定义与运行时逻辑相分离。 
与其他大多数标记语言不同,XAML 直接呈现托管对象的实例化。这种常规设计原则简化了使用 XAML 创建的对象的代码和调试访
问。 
XAML 有一组规则,这些规则将对象元素映射为类或结构,将属性 (Attribute) 映射为属性 (Property) 或事件,并将 XML 命名空
间映射为 CLR 命名空间。XAML 元素映射为被引用程序集中定义的 Microsoft .NET 类型,而属性 (Attribute) 则映射为这些类型
的成员。 
每个实例都是通过调用基础类或结构的默认构造函数并对结果进行存储而创建的。为了可用作 XAML 中的对象元素,该类或结构必须
公开一个公共的默认(无参数)构造函数。 
<Button.Content> 
This is a button 
</Button.Content> 
XAML 的属性 (Property) 元素语法表示了与标记的基本 XML 解释之间的巨大背离。对于 XML,<类型名称.属性> 代表了另一个
元素,该元素仅表示一个子元素,而与 TypeName 父级之间没有必然的隐含关系。在 XAML 中,<类型名称.Property> 直接表示 
Property 是类型名称 的属性(由属性元素内容设置),而绝不会是一个名称相似(碰巧名称中有一个点)但却截然不同的元素。 
引用值和标记扩展 

标记扩展是一个 XAML 概念。在属性语法中,花括号({ 和 })表示标记扩展用法。此用法指示 XAML 处理不要像通常那样将属性
值视为一个字符串或者可直接转换为文本字符串的值。 

WPF 应用程序编程中最常用的标记扩展是 Binding(用于数据绑定表达式)以及资源引用 StaticResource 和 DynamicResource。
通过使用标记扩展,即使属性 (Property) 不支持对直接对象实例化使用属性 (Attribute) 语法,也可以使用属性 (Attribute) 语法
为属性 (Property) 提供引用值 
资源只是 WPF 或 XAML 启用的一种标记扩展用法 
Typeconverter 的属性值: 但是很多 WPF 类型或这些类型的成员扩展了基本字符串属性处理行为,因此更复杂的对象类型的实例可
通过字符串指定为属性值 
该对象元素的任何 XML 子元素都被当作包含在一个表示该内容属性的隐式属性元素标记中来处理。在标记中,可以省略 XAML 内容
属性的属性元素语法。在标记中指定的任何子元素都将成为 XAML 内容属性的值。 
XAML 内容属性值必须连续 
XAML 处理器和序列化程序将忽略或删除所有无意义的空白,并规范化任何有意义的空白。只有当您在 XAML 内容属性中指定字符
串时,才会体现此行为的重要性。简言之,XAML 将空格、换行符和制表符转化为空格,如果它们出现在一个连续字符串的任一端,
则保留一个空格。 

一个 XAML 文件只能有一个根元素,这样才能成为格式正确的 XML 文件和有效的 XAML 文件。通常,应选择属于应用程序模型一
部分的元素(例如,为页面选择 Window 或 Page,为外部字典选择 ResourceDictionary,或为应用程序定义根选择 
Application)。下面的示例演示 WPF 页面的典型 XAML 文件的根元素,其中的根元素为 Page。 


代码隐藏、事件处理程序和分部类要求 
1分部类必须派生自用作根元素的类的类型。您可以在代码隐藏的分部类定义中将派生留空,但编译的结果会假定页根作为
分部类的基类,即使在没有指定的情况下也是如此(因为分部类的标记部分确实将页根指定为基)。 
2编写的事件处理程序必须是 x:Class 标识的命名空间中的分部类所定义的实例方法。您不能限定事件处理程序的名称来
指示 XAML 处理器在其他类范围中查找该处理程序,也不能将静态方法用作事件处理程序。 
3事件处理程序必须与相应事件的委托匹配。 
类要能够实例化为对象元素,必须满足以下要求: 

. 自定义类必须是公共的且支持默认(无参数)公共构造函数。(托管代码结构隐式支持这样的构造函数。) 
. 自定义类不能是嵌套类(嵌套类和其语法中的“点”会干扰其他 WPF 功能,例如附加属性)。 
. x:Class 可以声明为充当可扩展应用程序标记语言 (XAML) 元素树的根元素并且正在编译(可扩展应用程序标记语言 
(XAML) 通过 Page 生成操作包括在项目中)的任何元素的属性,也可以声明为已编译应用程序的应用程序定义中的 
Application 根的属性。在页面根元素或应用程序根元素之外的任何元素上以及在未编译的可扩展应用程序标记语言 
(XAML) 文件的任何环境下声明 x:Class 都会导致编译时错误。 
. 用作 x:Class 的类不能是嵌套类。 
. 完全可以在没有任何代码隐藏的情况下拥有 XAML 页,从这个角度而言,x:Class 是可选的,但是,如果页面声明了事件
处理属性值,或者实例化其定义类在代码隐藏类中的自定义元素,那么将最终需要为代码隐藏提供对适当类的 x:Class 引
用(或 x:Subclass)。 
. x:Class 属性的值必须是一个指定类的完全限定名的字符串。对于简单的应用程序,只要命名空间信息与代码隐藏的构建
方式相同(定义从类级别开始),就可以省略命名空间信息。页面或应用程序定义的代码隐藏文件必须在代码文件内,而该
代码文件应作为产生已编译应用程序的项目的一部分而包括在该项目中。必须遵循 CLR 类的命名规则;有关详细信息,请
参见 Type Definitions(类型定义)。默认情况下,代码隐藏类必须是 public 的,但也可以通过使用 x:ClassModifier 
属性定义为另一访问级别。 
. 请注意,x:Class 属性值的此含义是 WPF XAML 实现所特有的。WPF 外部的其他 XAML 实现可能不使用托管代码,因此
可能使用不同的类解析公式。 


 
x:Code 是在 XAML 中定义的一种指令元素。x:Code 指令元素可以包含内联编程代码。 
自定义类作为 XAML 元素的要求 

为了可方便地用作路由事件,CLR 事件应实现显式 add 和 remove 方法,这两种方法分别添加和移除 
CLR 事件签名的处理程序,并将这些处理程序转发到 AddHandler 和 RemoveHandler 方法 
XAML 处理器是指可根据其规范(通过编译或解释)将 XAML 接受为语言、并且可以生成结果基础类以供运行时对象模型使用(也
是根据 XAML 规范)的任意程序 
当用于提供属性 (Attribute) 值时,将标记扩展与 XAML 处理器区分开来的语法就是左右大括号({ 和 })。然后,由紧跟在左大括
号后面的字符串标记来标识标记扩展的类型。 

StaticResource 通过替换已定义资源的值来为 XAML 属性提供值 


DynamicResource 通过将值推迟为对资源的运行时引用来为 XAML 属性提供值 
Binding 按应用于元素的数据上下文来为属性提供数据绑定值。此标记扩展相对复杂,因为它会启用大量内联语法来指定数据绑定。
有关详细信息, 
通过 TemplateBinding,控件模板可以使用来自要利用该模板的类的对象模型定义属性中的模板化属性的值 
XAML 处理器中的属性处理使用大括号作为标记扩展的指示符。 
这些声明之间的关系是:XAML 实际上是语言标准,而 WPF 是将 XAML 作为语言使用的一个实现。XAML 语言指定一些为了兼容
而假定要实现的语言元素,每个元素都应当能通过针对 XAML 命名空间执行的 XAML 处理器实现进行访问。WPF 实现为其自己的 
API 保留默认命名空间,为 XAML 中需要的标记语法使用单独的映射前缀。按照约定,该前缀是 x:,此 x: 约定后面是项目模板、
示例代码和此 SDK 中语言功能的文档。XAML 命名空间定义了许多常用功能,这些功能即使对于基本的 WPF 应用程序也是必需的。
例如,若要通过分部类将任何代码隐藏加入 XAML 文件,您必须将该类命名为相关 XAML 文件的根元素中的 x:Class 属性。或者,
在 XAML 页中定义的、您希望作为键控资源访问的任何元素应当对相关元素设置了 x:Key 属性。 
映射到自定义类和程序集: clr-namespace: 在包含要作为元素公开的公共类型的程序集中声明的公共语言运行库 (CLR) 命名空间。 

assembly= 是指包含部分或全部引用的 CLR 命名空间的程序集。该值通常只是程序集的名称,而不是路径。该程序集的路径必须
在生成编译的 XAML 的项目文件中以项目引用的形式建立。另外,为了合并版本管理和强名称签名,该值也可以是 
AssemblyName 定义的字符串。 
请注意,分隔 clr-namespace 标记和其值的字符是冒号 (:),而分隔 assembly 标记和其值的字符是等号 (=)。这两个标记之间使
用的字符是分号。例如: 
xmlns:custom="clr-namespace:SDKSample;assembly=SDKSampleLibrary" 
唯一标识对象元素,以便于从代码隐藏或通用代码中访问实例化的元素。x:Name 一旦应用于支持编程模型,便可被视为与由构造函
数返回的用于保存对象引用的变量等效。 

x:Name 无法应用于某些范围。例如,ResourceDictionary 中的项不能有名称,因为它们已有作为唯一标识符的 x:Key 属性。 

因为在 WPF 命名空间为几个重要基类(如 FrameworkElement/FrameworkContentElement)指定的 Name 依赖项属性也具
有此用途。仍然有一些常见的 XAML 以及框架方案需要在不使用 Name 属性的情况下通过代码访问元素,这种情况在某些动画和演
示图板支持类中最为突出。例如,您应当在时间线以及在 XAML 中创建的转换上指定 x:Name,前提是您计划在代码中引用它们。 

如果 Name 定义为元素的一个属性,则 Name 和 x:Name 可互换使用,但如果在同一元素上同时指定了两者,将会产生错误。 
x:Static 标记扩展:引用以符合公共语言规范 (CLS) 的方式定义的任何静态的按值代码实体。引用的属性在加载 
XAML 页的余下部分之前计算,可用于以 XAML 提供 
<object> 
<object.property> 
<x:Static Member="prefix:typeName.staticMemberName" .../> 
</object.property> 

</object> 


http://i.msdn.microsoft.com/Platform/Controls/CollapsibleArea/resources/minus.gif
引用的代码实体必须是下面的某一项: 

. 常量 
. 静态属性 
. 字段 
. 枚举值 
. x:Subclass 用法主要针对不支持分部类声明的语言。 


派生类中的事件处理程序必须是 internal override(在 Microsoft Visual Basic .NET 中必须是 Friend Overrides),
才能重写编译期间在中间类中创建的处理程序的存根。否则,派生类实现将隐藏中间类实现,并且中间类处理程序无法被调
用。 

x:Type 本质上是 C# 中的 typeof() 运算符或 Microsoft Visual Basic .NET 中的 GetType 运算符的等效标记扩展。 
{} 转义序列用来对属性语法中用于标记扩展的 { 和 } 进行转义。严格来说,转义序列本身并不是标记扩展, 
WPF 命名空间 XAML 扩展 

 本节内容 

绑定标记扩展 

ColorConvertedBitmap 标记扩展 

ComponentResourceKey 标记扩展 

DynamicResource 标记扩展 

RelativeSource MarkupExtension 

StaticResource 标记扩展 

TemplateBinding 标记扩展 

ThemeDictionary 标记扩展 

PropertyPath XAML 语法 

PresentationOptions:Freeze 属性 
DynamicResource 标记扩展 

key 

所请求的资源的键。如果资源是在标记中创建的,则这个键最初是由 x:Key 属性分配的;如果资源是在代码中创建的,则
这个键是在调用 ResourceDictionary..::.Add 时作为 key 参数提供的。 



DynamicResource 将在初始编译过程中创建一个临时表达式,因而会将资源查找延迟到实际需要所请求的资源值来构造对象时才执
行。这可能是在加载 XAML 页之后。将基于键搜索在所有活动的资源字典中查找资源值(从当前页范围开始),并且资源值将取代编
译期间的占位符表达式。 


Windows Presentation Foundation (WPF) 中的大部分类都从四个类派生而来,这四个类在 SDK 文档中常常被称为基元素类。
这些类包括 UIElement、FrameworkElement、ContentElement 和 FrameworkContentElement。DependencyObject 也
是一个相关类,因为它是 UIElement 和 ContentElement 的通用基类。 

UIElement 和 ContentElement 都是从 DependencyObject 派生而来,但途径略有不同。此级别上的拆分涉及到 UIElement 或 
ContentElement 如何在用户界面上使用,以及它们在应用程序起到什么作用。UIElement 在其类层次结构中也有 Visual,该类为 
Windows Presentation Foundation (WPF) 公开较低级别的图形支持。Visual 通过定义独立的矩形屏幕区域来提供呈现框架。实
际上,UIElement 适用于支持大型数据模型的元素,这些元素用于在可以称为矩形屏幕区域的区域内进行呈现和布局,在该区域内,
内容模型特意设置得更加开放,以允许不同的元素进行组合。ContentElement 不是从 Visual 派生的;它的模型由其他对象(例如,
阅读器或查看器,用来解释元素并生成完整的 Visual 供 Windows Presentation Foundation (WPF) 使用)来使用 
ContentElement。某些 UIElement 类可用作内容宿主:它们为一个或多个 ContentElement 类(如 DocumentViewer)提供
宿主和呈现。ContentElement 用作以下元素的基类:所具有的对象模型较小,并且多用于寻址可能宿主在 UIElement 中的文本、
信息或文档内容。 
创建用于扩展 WPF 的自定义类的最实用方法是从某个 WPF 类中派生,这样您可以通过现有的类层次结构获得尽可能多的所需功能。
本节列出了三个最重要的元素类附带的功能,以帮助您决定要从哪个类进行派生。 

如果您要实现控件(这的确是从 WPF 类派生的更常见的原因之一),您可能需要从以下类中派生:实际控件、控件系列基类或至少
是 Control 基类 

DependencyObject 派生的类,则将继承以下功能: 

. GetValue 和 SetValue 支持以及一般的属性系统支持。 
. 使用依赖项属性以及作为依赖项属性实现的附加属性的能力。 


从 UIElement 派生的类,则除了能够继承 DependencyObject 提供的功能外,还将继承以下功能: 

对动画属性值的基本支持。有关更多信息,请参见动画概述。 

对基本输入事件和命令的支持。有关更多信息,请参见输入概述和命令概述。 
可以重写以便为布局系统提供信息的虚方法。 

FrameworkElement 派生的类,则除了能够继承 UIElement 提供的功能外,还将继承以下功能: 

. 对样式设置和演示图板的支持。有关更多信息,请参见 Style 和演示图板概述。 
. 对数据绑定的支持。有关更多信息,请参见数据绑定概述。 
. 对动态资源引用的支持。有关更多信息,请参见资源概述。 
. 对属性值继承以及元数据中有助于向框架服务报告属性的相关情况(如数据绑定、样式或布局的框架实现)的其他标志的支
持。有关更多信息,请参见框架属性元数据。 
. 逻辑树的概念。有关更多信息,请参见 WPF 中的树。 


对布局系统的实际 WPF 框架级实现的支持, 

ContentElement 派生的类,则除了能够继承 DependencyObject 提供的功能外,还将继承以下功能: 

. 对动画的支持。有关更多信息,请参见动画概述。 
. 对基本输入事件和命令的支持。有关更多信息 



FrameworkContentElement 派生的类,则除了能够继承 ContentElement 提供的功能外,还将获得以下功能: 

. 对样式设置和演示图板的支持。有关更多信息,请参见 Style 和动画概述。 
. 对数据绑定的支持。有关更多信息,请参见数据绑定概述。 
. 对动态资源引用的支持。有关更多信息,请参见资源概述。 
. 对属性值继承以及元数据中有助于向框架服务报告属性情况(如数据绑定、样式或布局的框架实现)的其他标志的支持。有
关更多信息,请参见框架属性元数据。 
. 您不会继承对布局系统修改(如 ArrangeOverride)的访问权限。布局系统实现只在 FrameworkElement 上提供。但
是,您会继承 OnPropertyChanged 重写(可以检测影响布局的属性更改并将这些更改报告给任何内容宿主)。 


DispatcherObject 

DispatcherObject 为 WPF 线程模型提供支持,并允许为 WPF 应用程序创建的所有对象与 Dispatcher 相关联。即使您不从 
UIElement, DependencyObject 或 Visual 派生,也应考虑从 DispatcherObject 派生,以获得此线程模型支持 

Visual 

Visual 实现二维对象在近似矩形的区域中通常需要具有可视化表示的概念。Visual 的实际呈现发生在其他类中(不是独立的),但
是 Visual 类提供了一个由各种级别的呈现处理使用的已知类型。 

Freezable 对象的示例包括画笔、钢笔、变换、几何图形和动画。Freezable 提供了一个 Changed 事件以将对对象所做
的任何修改通知给观察程序。冻结 Freezable 可以改进其性能,因为它不再需要因更改通知而消耗资源。冻结的 
Freezable 也可以在线程之间共享,而解冻的 Freezable 则不能。 

并不是每个 Freezable 对象都可以冻结。若要避免引发 InvalidOperationException,请在尝试冻结 Freezable 对象之前,查看
其 CanFreeze 属性的值,以确定它是否可以被冻结。 
当不再需要修改某个 Freezable 时,冻结它可以改进性能。如果您在该示例中冻结画笔,则图形系统将不再需要监视它的
更改情况。图形系统还可以进行其他优化,因为它知道画笔不会更改。 
SolidColorBrush a = new SolidColorBrush(Colors.Yellow ); 
if (a.CanFreeze) 
{ 
a.Freeze(); 
} 
如果下列任一情况属实,则无法冻结 Freezable: 

. 它有动画或数据绑定的属性。 
. 它有由动态资源设置的属性 (有关动态资源的更多信息,请参见资源概述)。 
. 它包含无法冻结的 Freezable 子对象。 


为了避免引发此异常,可以使用 IsFrozen 方法来确定 Freezable 是否处于冻结状态。 
if (myBrush.IsFrozen) // Evaluates to true. 


在前面的代码示例中,使用 Clone 方法对一个冻结的对象创建了可修改副本。下一节将更详细地讨论克隆操作。 

从标记冻结. 


若要冻结在标记中声明的 Freezable 对象,请使用 PresentationOptions:Freeze 属性。在下面的示例中,将一个 
SolidColorBrush 声明为页资源,并冻结它。随后将它用于设置按钮的背景。 
<Page.Resources> 
<!-- This resource is frozen. --> 
<SolidColorBrush 
x:Key="MyBrush" 
PresentationOptions:Freeze="True" 
Color="Red" /> 
</Page.Resources> 
若要使用 Freeze 属性,必须映射到表示选项命名空间:
http://schemas.microsoft.com/winfx/2006/xaml/presentation/options。PresentationOptions 是用于映射该命名空间的
推荐前缀: 

 xmlns:PresentationOptions=http://schemas.microsoft.com/winfx/2006/xaml/presentation/options 

由于并非所有 XAML 读取器都能识别该属性,因此建议使用 mc:Ignorable 属性 将 Presentation:Freeze 属性标记为可忽略: 

 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
mc:Ignorable="PresentationOptions" 

Freezable 一旦冻结,便不能再修改或解冻;不过,您可以使用 Clone 或 CloneCurrentValue 方法创建一个解冻的复本 

从 Freezable 派生的类可以获取以下功能。 

. 特殊的状态:只读(冻结)状态和可写状态。 
. 线程安全:冻结的 Freezable 可以在线程之间共享。 
. 详细的更改通知:与其他 DependencyObject 不同,Freezable 对象会在子属性值更改时提供更改通知。 
. 轻松克隆:Freezable 类已经实现了多种生成深层复本的方法。 


每个 Freezable 子类都必须重写 CreateInstanceCore 方法。如果您的类对于其所有数据都使用依赖项属性,则您的工作已完成。 

FrameworkElement 类公开一些用于精确定位子元素的属性。本主题论述其中四个最重要的属性:HorizontalAlignment、
Margin、Padding 和 VerticalAlignment。务必要了解这些属性的作用,因为这些属性是控制元素在 Windows Presentation 
Foundation (WPF) 应用程序中的位置的基础。 

在一个元素上显式设置的 Height 和 Width 属性优先于 Stretch 属性值。如果尝试设置 Height, Width 以及 Stretch 的 
HorizontalAlignment 值,将会导致 Stretch 请求被忽略。 

Padding 在大多数方面类似于 Margin。Padding 属性只会在少数类上公开,主要是为了方便起见而公开:Block、Border、
Control 和 TextBlock 是公开 Padding 属性的类的示例。Padding 属性可将子元素的有效大小增大指定的 Thickness 值。 



元素树和序列化:WPF 编程元素彼此之间通常以某种形式的树关系存在。例如,在 XAML 中创建的应用程序 UI 可以被概念
化为一个元素树。可以进一步将元素树分为两个离散但有时会并行的树:逻辑树和可视化树。WPF 中的序列化涉及到保存这两个树
和应用程序的状态并将状态写入文件(通常以 XAML 形式)。 
WPF 中主要的树结构是元素树。如果使用 XAML 创建应用程序页,则将基于标记中元素的嵌套关系创建树结构。如果使用代码创建
应用程序,则将基于为属性(实现给定元素的内容模型)指定属性值的方式创建树结构。在 Windows Presentation Foundation 
(WPF) 中,处理和使用概念说明元素树的方法实际上有两种:即逻辑树和可视化树。逻辑树与可视化树之间的区别并不始终很重要,
但在某些 WPF 子系统中它们可能会偶尔导致问题,并影响您对标记或代码的选择。 
逻辑树 

在 WPF 中,可使用属性向元素中添加内容。例如,使用 ListBox 控件的 Items 属性可向该控件中添加项。通过此方式,可将项放
置到 ListBox 控件的 ItemCollection 中。若要向 DockPanel 中添加元素,可使用其 Children 属性。此时,将向 DockPanel 的 
UIElementCollection 中添加元素 

逻辑树用途 

逻辑树的存在用途是使内容模型可以容易地循环访问其可能包含的子元素,从而可以对内容模型进行扩展。此外,逻辑树还为某些通
知提供了框架,例如当加载逻辑树中的所有元素时。 

此外,在 Resources 集合的逻辑树中首先向上查找初始请求元素,然后再查找父元素,这样可以解析资源引用。当同时存在逻辑树
和可视化树时,将使用逻辑树进行资源查找 

属性值继承 

属性值继承通过混合树操作。包含用于启用属性继承的 Inherits 属性的实际元数据是 WPF 框架级
别 FrameworkPropertyMetadata 类。因此,保留原始值的父元素以及继承该父元素的子元素都必须是 FrameworkElement 或 
FrameworkContentElement,并且它们都必须属于某个逻辑树的一部分。但是,允许父元素的逻辑树与子元素的逻辑树相互独立,
这样可以通过一个不在逻辑树中的中介可视元素使属性值继承永续进行。若要使属性值继承在这样的界限中以一致的方式工作,必须
将继承属性注册为附加属性。通过帮助器类实用工具方法无法完全预测属性继承确切使用的树,即使在运行时也一样。有关更多信息,
请参见属性值继承。 
可视化树 

WPF 中除了逻辑树的概念,还存在可视化树的概念。可视化树描述由 Visual 基类表示的可视化对象的结构。为控件编写模板时,将
定义或重新定义适用于该控件的可视化树。对于出于性能和优化原因想要对绘图进行较低级别控制的开发人员来说,他们也会对可视
化树感兴趣。作为常规 WPF 应用程序编程一部分的可视化树的一个公开情况是,路由事件的事件路由大多数情况下遍历可视化树,
而不是逻辑树。这种微妙的路由事件行为可能不会很明显,除非您是控件作者。在可视化树中路由使得在可视化级别实现组合的控件
能够处理事件或创建事件 setter。 
树、内容元素和内容宿主 

内容元素(从 ContentElement 派生的类)不是可视化树的一部分;内容元素不从 Visual 继承并且没有可视化表示形式。若要完全
在 UI 中显示,则必须在既是 Visual,也是逻辑树元素(通常是 FrameworkElement)的内容宿主中承载 ContentElement。您
可以使用概念说明,内容宿主有点类似于内容的“浏览器”,它选择要在该宿主控制的屏幕区域中显示内容的方式。承载内容时,可以
使内容成为通常与可视化树关联的某些树进程的参与者。通常,FrameworkElement 宿主类包括实现代码,该代码用于通过内容逻
辑树的子节点将任何已承载的 ContentElement 添加到事件路由,即使承载内容不是真实可视化树的一部分时也将如此。这样做是
必要的,以便 ContentElement 可以为路由到除其自身之外的任何元素的路由事件提供来源。 

LogicalTreeHelper 类为逻辑树遍历提供 GetChildren、GetParent 和 FindLogicalNode 方法。在大多数情况下 


资源和树 

资源查找基本上遍历逻辑树。不在逻辑树中的对象可以引用资源,但查找将从该对象连接到逻辑树的位置开始。仅逻辑树节点可以有
包含 ResourceDictionary 的 Resources 属性,因此这意味着,遍历可视化树来查找资源没有好处。 
但是,资源查找也可以超出直接逻辑树。 
序列化的结果是应用程序的结构化逻辑树的有效表示形式,但并不一定是生成该树的原始 XAML。 

Save 的序列化输出是独立的;序列化的所有内容都包含在单个 XAML 页面中,该页面具有单个根元素而且没有除 URI 以外的外部
引用。例如,如果您的页面从应用程序资源引用了资源,则这些资源看上去如同正在进行序列化的页面的一个组件 
因为序列化是独立的并局限于逻辑树,所以没有工具可用于存储事件处理程序 
应注意 Windows Presentation Foundation (WPF) 类的对象初始化的几个方面特意不在调用类构造函数时所执行的代码的某一部
分实现。这种情况对于控件类尤为突出,该控件的大部分可视化表示都不是由构造函数定义的,而是由控件的模板定义的。模板可能
来自于各种源,但是最常见的情况是来自于主题样式。模板实际上是后期绑定的;只有在相应的控件已准备好应用布局时,才会将所
需的模板附加到该控件上。 

 如果针对其设置属性的元素是 FrameworkElement 或 FrameworkContentElement 派生类,则您可以调用 BeginInit 和 
EndInit 的类版本,而不是强制转换为 ISupportInitialize。 
WPF) 提供了一组服务,这些服务可用于扩展公共语言运行库 (CLR) 属性的功能。这些服务通常统称为 WPF 属性系统。由 WPF 属
性系统支持的属性称为依赖项属性。本概述介绍 WPF 属性系统以及依赖项属性的功能 
依赖项属性的用途在于提供一种方法来基于其他输入的值计算属性值。这些其他输入可以包括系统属性(如主题和用户首选项)、实
时属性确定机制(如数据绑定和动画/演示图板)、重用模板(如资源和样式)或者通过与元素树中其他元素的父子关系来公开的值。
另外,可以通过实现依赖项属性来提供独立验证、默认值、监视其他属性的更改的回调以及可以基于可能的运行时信息来强制指定属
性值的系统。派生类还可以通过重写依赖项属性元数据(而不是重写现有属性的实际实现或者创建新属性)来更改现有属性的某些具
体特征。 

定义 WPF 属性系统的另一个重要类型是 DependencyObject。DependencyObject 定义可以注册和拥有依赖项属性的基类。 

. 依赖项属性:一个由 DependencyProperty 支持的属性。 
. 依赖项属性标识符:一个 DependencyProperty 实例,在注册依赖项属性时作为返回值获得,之后将存储为一个类成员。
在与 WPF 属性系统交互的许多 API 中,此标识符用作一个参数。 


属性以及支持它的 DependencyProperty 字段的命名约定非常重要。字段总是与属性同名,但其后面追加了 Property 后缀。 
<Button.Background> 
<ImageBrush ImageSource="wavy.jpg"/> 
</Button.Background> 
</Button> 
在代码中设置依赖项属性值通常只是调用由 CLR“包装”公开的 set 实现。获取属性值实质上也是在调用 get“包装”实现: 

. 依赖项属性提供用来扩展属性功能的功能,这与字段支持的属性相反。每个这样的功能通常都表示或支持整套 WPF 功能中
的特定功能 
资源 数据绑定 样式 动画 元数据重写 属性值继承 WPF 设计器集成 
. <DockPanel.Resources> 
. <SolidColorBrush x:Key="MyBrush" Color="Gold"/> 



. </DockPanel.Resources> 


在定义了某个资源之后,可以引用该资源并使用它来提供属性值: 
<Button Background="{DynamicResource MyBrush}" Content="I am gold" /> 
//StaticResource 
<Style x:Key="GreenButtonStyle"> 
<Setter Property="Control.Background" Value="Green"/> 
</Style> 
//
<Button Style="{StaticResource GreenButtonStyle}">I am green!</Button> 
资源被视为本地值,这意味着,如果您设置另一个本地值,该资源引用将被消除 
绑定被视为本地值,这意味着,如果您设置另一个本地值,该绑定将被消除 
<Button>I am animated 
<Button.Background> 
<SolidColorBrush x:Name="AnimBrush"/> 
</Button.Background> 
<Button.Triggers> 
<EventTrigger RoutedEvent="Button.Loaded"> 
<BeginStoryboard> 
<Storyboard> 
<ColorAnimation 
Storyboard.TargetName="AnimBrush" 
Storyboard.TargetProperty="(SolidColorBrush.Color)" 
From="Red" To="Green" Duration="0:0:5" 
AutoReverse="True" RepeatBehavior="Forever" /> 
</Storyboard> 
</BeginStoryboard> 
</EventTrigger> 
</Button.Triggers> 
</Button> 

属性值继承 

元素可以从其在树中的父级继承依赖项属性的值。 

说明: 

属性值继承行为并未针对所有的依赖项属性在全局启用,因为继承的计算时间确实会对性能产生一定的影响。属性值继承通常只
有在特定方案指出适合使用属性值继承时才对属性启用 



 
作用一类Button的property: 


<StackPanel> 
<StackPanel.Resources> 
<Style TargetType="{x:Type Button}"> 
<Setter Property="Background" Value="Red"/> 
</Style> 
</StackPanel.Resources> 
<Button Background="Green">I am NOT red!</Button> 
<Button>I am styled red</Button> 
</StackPanel> 

为什么存在依赖项属性优先级? 

通常,您不会希望总是应用样式,而且不希望样式遮盖单个元素的哪怕一个本地设置值(否则,通常将很难使用样式或元素)。因此,
来自样式的值的操作优先级将低于本地设置的值 
附加属性是一种类型的属性,它支持 XAML 中的专用语法。附加属性通常与公共语言运行库 (CLR) 属性不具有 1:1 对应关系,而且
不一定是依赖项属性。附加属性的典型用途是使子元素可以向其父元素报告属性值,即使父元素和子元素的类成员列表中均没有该属
性也是如此。附加属性旨在用作可在任何对象上设置的一类全局属性。在 Windows Presentation Foundation (WPF) 中,附加属
性通常定义为没有常规属性“包装”的一种特殊形式的依赖项属性。 
。附加属性是一个 XAML 概念, 而依赖项属性则是一个 WPF 概念 

附加属性的一个用途是允许不同的子元素为实际在父元素中定义的属性指定唯一值。此方案的一个具体应用是让子元素通知父元素它
们将如何在用户界面 (UI) 中呈现。一个示例是 DockPanel..::.Dock 属性。DockPanel..::.Dock 属性创建为附加属性,因为它将
在 DockPanel 中包含的元素上设置,而不是在 DockPanel 本身设置。DockPanel 类定义名为 DockProperty 的静态 
DependencyProperty 字段,然后将 GetDock 和 SetDock 方法作为该附加属性的公共访问器提供。 
定义附加属性的类型通常采用以下模型之一: 

. 设计定义附加属性的类型,以便它可以是将为附加属性设置值的元素的父元素。之后,该类型将在内部逻辑中循环访问其子
元素,获取值,并以某种方式作用于这些值。 
. 定义附加属性的类型将用作各种可能的父元素和内容模型的子元素。 
. 定义附加属性的类型表示一个服务。其他类型为该附加属性设置值。之后,当在服务的上下文中计算设置该属性的元素时,
将通过服务类的内部逻辑获取附加属性的值。 


如果您希望对属性启用属性值继承,则应使用附加属性,而不是非附加的依赖项属性。有关详细信息 
DockPanel myDockPanel = new DockPanel(); 
CheckBox myCheckBox = new CheckBox(); 
myCheckBox.Content = "Hello"; 
myDockPanel.Children.Add(myCheckBox); 
DockPanel.SetDock(myCheckBox, Dock.Top); 

何时创建附加属性 

当确实需要有一个可用于定义类之外的其他类的属性设置机制时,您可能会创建附加属性。这种情况的最常见方案是布局。现有布局
属性的示例有 DockPanel..::.Dock、Panel..::.ZIndex 和 Canvas..::.Top。这里启用的方案是作为布局控制元素的子元素存在的
元素能够分别向其布局父元素表达布局要求,其中每个元素都设置一个被父级定义为附加属性的属性值 
前面已提到,如果您希望使用属性值继承,应注册为一个附加属性。 

如何创建附加属性 

如果您的类将附加属性严格定义为用于其他类型,那么该类不必从 DependencyObject 派生。但是,如果您遵循使附加属性同时也
是一个依赖项属性的整体 WPF 模型,则需要从 DependencyObject 派生。 


http://cajon.cnblogs.com/Images/OutliningIndicators/None.gif
public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached( 
"IsBubbleSource", 
typeof(Boolean), 
typeof(AquariumObject), 
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender) 
); 
public static void SetIsBubbleSource(UIElement element, Boolean value) 
{ 
element.SetValue(IsBubbleSourceProperty, value); 
} 
public static Boolean GetIsBubbleSource(UIElement element) 
{ 
return (Boolean)element.GetValue(IsBubbleSourceProperty); 
} 


DependencyProperty和DependencyObject配合,提供了WPF中基本的数据存储、访问和通知的机制。也正是因为这两个东西
的存在,使得XAML,Binding,Animation都成为可能。 
public static readonly DependencyProperty CounterProperty = 
DependencyProperty.Register( 
"PropertyName", // 属性名 
typeof(int), // 属性的类型 
typeof(MyButtonSimple), // 属性所在的类型 
new PropertyMetadata(0) // 属性的默认值 


 ) 
不会强制属性的默认值。如果属性值仍然采用其初始默认值,或通过使用 ClearValue 清除其他值,则可能存在等于默认值的属性
值。 
什么是依赖项属性? 
您可以启用本应为公共语言运行库 (CLR) 属性的属性来支持样式设置、数据绑定、继承、动画和默认值, 

依赖项属性只能由 DependencyObject 类型使用, WPF 中的所有依赖项属性(大多数附加属性除外)也是 CLR 属性 
在类体中定义依赖项属性是典型的实现,但是也可以在类静态构造函数中定义依赖项属性。如果您需要多行代码来初始化依赖项属性,
则此方法可能会很有意义。 

如果要创建在 FrameworkElement 的派生类上存在的依赖项属性,则可以使用更专用的元数据类 FrameworkPropertyMetadata,
而不是 PropertyMetadata 基类。 
WPF 属性系统提供了一个强大的方法,使得依赖项属性的值由多种因素决定,从而实现了诸如实时属性验证、后期绑定以及向相关
属性发出有关其他属性值发生更改的通知等功能 
本地属性集在设置时具有最高优先级,动画值和强制转换除外。如果您在本地设置某个值,一定会希望该值能优先得到应用,甚至希
望其优先级高于任何样式或控件模板 
<Button > 
<Button.Style> 
<Style TargetType="{x:Type Button}"> 
<Setter Property="Background" Value="Green"/> 
<Style.Triggers> 
<Trigger Property="IsMouseOver" Value="True"> 
<Setter Property="Background" Value="Blue" /> 


 </Trigger> 
</Style.Triggers> 
</Style> 
</Button.Style> 
Click 
</Button> 
依赖项属性设置优先级列表 

1. 属性系统强制转换。 有关强制转换的详细信息, 
2. 活动动画或具有 Hold 行为的动画。 为了获得任何实用效果,属性的动画必须优先于基(未动画)值,即使该值是在本地设置
的情况下也将如此。有关详细信息, 
3. 本地值。 本地值可以通过“包装”属性 (Property) 的便利性进行设置,这也相当于在 XAML 中设置属性 (Attribute) 或属性 
(Property) 元素,或者使用特定实例的属性调用 SetValue API。如果您使用绑定或资源来设置本地值,则每个值都按照直接设
置值的优先级顺序来应用。 
4. TemplatedParent 模板属性。 如果元素是作为模板(ControlTemplate 或 DataTemplate)的一部分创建的,则具有 
TemplatedParent。有关何时应用此原则的详细信息,请参见本主题后面的 TemplatedParent。在模板中,按以下优先级顺序
应用: 
a. 来自 TemplatedParent 模板的触发器。 
b. TemplatedParent 模板中的属性 (Property) 集。(通常通过 XAML 属性 (Attribute) 进行设置。) 


5. 隐式样式。 仅应用于 Style 属性。Style 属性是由任何样式资源通过与其类型匹配的键来填充的。该样式资源必须存在于页面或
应用程序中;查找隐式样式资源不会进入到主题中。 
6. 样式触发器。 来自页面或应用程序上的样式中的触发器。(这些样式可以是显式或隐式样式,但不是来自优先级较低的默认样
式。) 
7. 模板触发器。 来自样式中的模板或者直接应用的模板的任何触发器。 
8. 样式 Setter。 来自页面或应用程序的样式中的 Setter 的值。 
9. 默认(主题)样式。 有关何时应用此样式以及主题样式如何与主题样式中的模板相关的详细信息, 
a. 主题样式中的活动触发器。 
b. 主题样式中的 Setter。 


10. 继承。有几个依赖项属性从父元素向子元素继承值,因此不需要在应用程序中的每个元素上专门设置这些属性。 
11. 来自依赖项属性元数据的默认值。 任何给定的依赖项属性都具有一个默认值,它由该特定属性的属性系统注册来确定。而且,继
承依赖项属性的派生类具有按照类型重写该元数据(包括默认值)的选项。有关更多信息, 。因为继承是在默认值之前检查的,
所以对于继承的属性,父元素的默认值优先于子元素。 因此,如果任何地方都没有设置可继承的属性,将使用在根元素或父元素
中指定的默认值,而不是子元素的默认值。 


WPF 附带的每个控件都有一个默认样式,在默认样式中,对于控件最重要的信息就是其控件模板, 控件常常在主题中将触发器行为定
义为其默认样式的一部分, 为控件设置本地属性可能会阻止触发器从视觉或行为上响应用户驱动的事件。 

ClearValue 方法为从在元素上设置的依赖项属性中清除任何本地应用的值提供了一个有利的途径 

属性值继承 


属性值继承是包容继承 
父元素还可以通过属性值继承来获得其值,因此系统有可能一直递归到页面根元素。属性值继承不是属性系统的默认行为;属性必须
用特定的元数据设置来建立,以便使该属性能够对子元素启动属性值继承。属性值继承则是关于属性值如何基于元素树中的父子关系
从一个元素继承到另一个元素。通过更改自定义属性的元数据,还可以使您自己的自定义属性可继承. 
附加属性的典型方案是针对子元素设置属性值,如果所讨论的属性是附加属性 
属性继承通过遍历元素树来工作。此树通常与逻辑树平行。跨树边界继承属性值(属性值继承就可以弥合逻辑树中的这种间隙,而且仍
可以传递所继承的值) 
为了纠正此问题,在类构造函数调用中,必须将集合依赖项属性值重设为唯一的实例 
当您定义自己的属性并需要它们支持 Windows Presentation Foundation (WPF) 功能的诸多方面(包括样式、数据绑定、继承、
动画和默认值)时,应将其实现为依赖项属性。 
设计依赖项属性: 
public class MyStateControl : ButtonBase 
{ 
public MyStateControl() : base() { } 
public Boolean State 
{ 
get { return (Boolean)this.GetValue(StateProperty); } 
set { this.SetValue(StateProperty, value); } 
} 
public static readonly DependencyProperty StateProperty = DependencyProperty.Register( 
"State", typeof(Boolean), typeof(MyStateControl),new PropertyMetadata(false)); 
} 
设计附加属性: 
public static readonly DependencyProperty IsBubbleSourceProperty = DependencyProperty.RegisterAttached( 
"IsBubbleSource", 
typeof(Boolean), 
typeof(AquariumObject), 
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender) 
); 
public static void SetIsBubbleSource(UIElement element, Boolean value) 
{ 
element.SetValue(IsBubbleSourceProperty, value); 
} 
public static Boolean GetIsBubbleSource(UIElement element) 
{ 
return (Boolean)element.GetValue(IsBubbleSourceProperty); 
} 
可以从功能或实现的角度来考虑路由事件。此处对这两种定义均进行了说明,因为用户当中有的认为前者更有用,而有的则认为后者
更有用。 
路由事件:功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件。 

实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统
来处理。 

路由事件的顶级方案 

下面简要概述了需运用路由事件的方案,以及为什么典型的 CLR 事件不适合这些方案: 


控件的撰写和封装:WPF 中的各个控件都有一个丰富的内容模型。例如,可以将图像放在 Button 的内部,这会有效地扩展按钮的
可视化树。但是,所添加的图像不得中断命中测试行为(该行为会使按钮响应对图像内容的 Click),即使用户所单击的像素在技术
上属于该图像也是如此 
单一处理程序附加点:在 Windows 窗体中,必须多次附加同一个处理程序,才能处理可能是从多个元素引发的事件。路由事件使您
可以只附加该处理程序一次(像上例中那样),并在必要时使用处理程序逻辑来确定该事件源自何处。例如,这可以是前面显示的 
XAML 的处理程序: 
类处理:路由事件允许使用由类定义的静态处理程序。这个类处理程序能够抢在任何附加的实例处理程序之前来处理事件。 

引用事件,而不反射:某些代码和标记技术需要能标识特定事件的方法。路由事件创建 RoutedEvent 字段作为标识符,以此提供不
需要静态反射或运行时反射的可靠的事件标识技术。 
路由事件使用以下三个路由策略之一: 

. 冒泡:针对事件源调用事件处理程序。路由事件随后会路由到后续的父元素,直到到达元素树的根。大多数路由事件都使
用冒泡路由策略。冒泡路由事件通常用来报告来自不同控件或其他 UI 元素的输入或状态变化。 
. 直接:只有源元素本身才有机会调用处理程序以进行响应。这与 Windows 窗体用于事件的“路由”相似。但是,与标准 
CLR 事件不同的是,直接路由事件支持类处理(类处理将在下一节中介绍)而且可以由 EventSetter 和 EventTrigger 
使用。 
. 隧道:最初将在元素树的根处调用事件处理程序。随后,路由事件将朝着路由事件的源节点元素(即引发路由事件的元素)
方向,沿路由线路传播到后续的子元素。在合成控件的过程中通常会使用或处理隧道路由事件,这样,就可以有意地禁止显
示复合部件中的事件,或者将其替换为特定于整个控件的事件。在 WPF 中提供的输入事件通常是以隧道/冒泡对实现的。
隧道事件有时又称作 Preview 事件,这是由隧道/冒泡对所使用的命名约定决定的。 


如果您使用以下任一建议方案,路由事件的功能将得到充分发挥:在公用根处定义公用处理程序、合成自己的控件或者定义您自己的
自定义控件类。路由事件还可以用来通过元素树进行通信,因为事件的事件数据会永存到路由中的每个元素中。一个元素可以更改事
件数据中的某项内容,该更改将对于路由中的下一个元素可用。 

. 某些 WPF 样式和模板功能(如 EventSetter 和 EventTrigger)要求所引用的事件是路由事件。前面提到的事件标识符方
案就是这样的。 
. 路由事件支持类处理机制,类可以凭借该机制来指定静态方法,这些静态方法能够在任何已注册的实例程序访问路由事件之前,
处理这些路由事件。这在控件设计中非常有用,因为您的类可以强制执行事件驱动的类行为,以防它们在处理实例上的事件时
被意外禁止。 
. <Button Click="b1SetColor">button</Button> 
. 
. b1SetColor 是所实现的处理程序的名称,该处理程序中包含用来处理 Click 事件的代码。b1SetColor 必须具有与 
RoutedEventHandler 委托相同的签名,该委托是 Click 事件的事件处理程序委托。所有路由事件处理程序委托的第一个参
数都指定要向其中添加事件处理程序的元素,第二个参数指定事件的数据。 
. entHandler 是基本的路由事件处理程序委托 
. 
. void MakeButton() 


 { 

. Button b2 = new Button(); 


 b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click)); 
} 


 void MakeButton2() 
{ 
Button b2 = new Button(); 
b2.Click += new RoutedEventHandler(Onb2Click2); 
} 

“已处理”概念 

所有的路由事件都共享一个公用的事件数据基类 RoutedEventArgs。RoutedEventArgs 定义了一个采用布尔值的 Handled 属性。
Handled 属性的目的在于,允许路由中的任何事件处理程序通过将 Handled 的值设置为 true 来将路由事件标记为“已处理”。处理
程序在路由路径上的某个元素处对共享事件数据进行处理之后,这些数据将再次报告给路由路径上的每个侦听器。 

Handled 的值影响路由事件在沿路由线路向远处传播时的报告或处理方式。在路由事件的事件数据中,如果 Handled 为 true,则
通常不再为该特定事件实例调用负责在其他元素上侦听该路由事件的处理程序。这条规则对以下两类处理程序均适用:在 XAML 中附
加的处理程序;由语言特定的事件处理程序附加语法(如 += 或 Handles)添加的处理程序。对于最常见的处理程序方案,如果将 
Handled 设置为 true,以此将事件标记为“已处理”,则将“停止”隧道路由或冒泡路由,同时,类处理程序在某个路由点处处理的所
有事件的路由也将“停止”。 

但是,侦听器仍可以凭借“handledEventsToo”机制来运行处理程序,以便在事件数据中的 Handled 为 true 时响应路由事件。换
言之,将事件数据标记为“已处理”并不会真的停止事件路由。您只能在代码或 EventSetter 中使用 handledEventsToo 机制: 

在代码中,不使用适用于一般 CLR 事件的特定于语言的事件语法,而是通过调用 WPF 方法 AddHandler(RoutedEvent, 
Delegate, Boolean) 来添加处理程序。使用此方法时,请将 handledEventsToo 的值指定为 true。 

在 EventSetter 中,请将 HandledEventsToo 属性设置为 true。 
WPF 中的附加事件 
XAML 语言还定义了一个名为“附加事件”的特殊类型的事件。使用附加事件,可以将特定事件的处理程序添加到任意元素中。
正在处理该事件的元素不必定义或继承附加事件,可能引发这个特定事件的对象和用来处理实例的目标也都不必将该事件定
义为类成员或将其作为类成员来“拥有”。 

WPF 输入系统广泛地使用附加事件。但是,几乎所有的附加事件都是通过基本元素转发的。输入事件随后会显示为等效的、
作为基本元素类成员的非附加路由事件。例如,通过针对该 UIElement 使用 MouseDown(而不是在 XAML 或代码中处
理附加事件语法),可以针对任何给定的 UIElement 更方便地处理基础附加事件 Mouse..::.MouseDown。 

这两个事件会共享同一个事件数据实例,因为用来引发冒泡事件的实现类中的 RaiseEvent 方法调用会侦听隧道事件中的事
件数据并在新引发的事件中重用它。具有隧道事件处理程序的侦听器首先获得将路由事件标记为“已处理”的机会(先是类处
理程序,后是实例处理程序)。如果隧道路由中的某个元素将路由事件标记为“已处理”,则会针对冒泡事件发送已经处理的
事件数据,而且将不调用为等效的冒泡输入事件附加的典型处理程序。已处理的冒泡事件看起来好像尚未引发过。此处理行
为对于控件合成非常有用,因为此时您可能希望所有基于命中测试的输入事件或者所有基于焦点的输入事件都由最终的控件
(而不是它的复合部件)报告。作为可支持控件类的代码的一部分,最后一个控件元素靠近合成链中的根,因此将有机会首
先对隧道事件进行类处理,或许还有机会将该路由事件“替换”为更特定于控件的事件。 


事件路由示意图 


1. 针对根元素处理 PreviewMouseDown(隧道)。 
2. 针对中间元素 1 处理 PreviewMouseDown(隧道)。 
3. 针对源元素 2 处理 PreviewMouseDown(隧道)。 
4. 针对源元素 2 处理 MouseDown(冒泡)。 
5. 针对中间元素 1 处理 MouseDown(冒泡)。 
6. 针对根元素处理 MouseDown(冒泡)。 


路由事件处理程序委托提供对以下两个对象的引用:引发该事件的对象以及在其中调用处理程序的对象。在其中调用处理程序的对象
是由 sender 参数报告的对象。首先在其中引发事件的对象是由事件数据中的 Source 属性报告的。路由事件仍可以由同一个对象引
发和处理,在这种情况下,sender 和 Source 是相同的(事件处理示例列表中的步骤 34 就是这样的情况)。 
通常,隧道事件和冒泡事件之间的共享事件数据模型以及先引发隧道事件后引发冒泡事件等概念并非对于所有的路由事件都适用。该
行为的实现取决于 WPF 输入设备选择引发和连接输入事件对的具体方式。实现自己的输入事件是一个高级方案,但是您也可以选择
针对自己的输入事件遵循该模型。 
<StackPanel > 
<StackPanel.Resources > 
<Style TargetType="{x:Type Button}" > 
<EventSetter Event="Click" Handler="b1SetColor" /> 
</Style> 
</StackPanel.Resources> 
<Button >click me</Button> 
<Button Click="HandleThis" >buton make</Button> 
</StackPanel> 

另一个将 WPF 的路由事件和动画功能结合在一起的专用语法是 EventTrigger。与 EventSetter 一样,只有路由事件可以用于 
EventTrigger。通常将 EventTrigger 声明为样式的一部分,但是还可以在页面级元素上将 EventTrigger 声明为 Triggers 集合
的一部分或者在 ControlTemplate 中对其进行声明。使用 EventTrigger,可以指定当路由事件到达其路由中的某个元素(这个元
素针对该事件声明了 EventTrigger)时将运行的 Storyboard。与只是处理事件并且会导致它启动现有演示图板相比,
EventTrigger 的好处在于,EventTrigger 对演示图板及其运行时行为提供更好的控制 


附加事件概述 
可扩展应用程序标记语言 (XAML) 定义了一个语言组件和称为“附加事件”的事件类型。附加事件的概念允许您针对特定事件为任意元
素(而不是为实际定义或继承该事件的元素)添加处理程序。在这种情况下,对象既不会引发该事件,目标处理实例也不会定义或“拥
有”该事件。 

附加事件具有一种 XAML 语法和编码模式,后备代码必须使用该语法和编码模式才支持附加事件的使用, 在 WPF 中,附加事件由 
RoutedEvent 字段来支持,并在引发后通过元素树进行路由。通常,附加事件的源(引发该事件的对象)是系统或服务源,所以运
行引发该事件的代码的对象并不是元素树的直接组成部分。 

Microsoft .NET Framework 托管代码中的所有对象都要经历类似的一系列的生命阶段,即创造、使用和析构, 在 WPF 中有四种与
生存期事件有关的主要类型的对象;即常规元素、窗口元素、导航宿主和应用程序对象。任何 WPF 框架级元素(从 
FrameworkElement 或 FrameworkContentElement 派生的那些对象)都有三种通用的生存期事件: Initialized、Loaded 和 
Unloaded。 

引发 Loaded 事件的机制不同于 Initialized。将逐个元素引发 Initialized 事件,而无需通过整个元素树直接协调。相反,引发 
Loaded 事件是在整个元素树内协调的结果(特别是逻辑树)。当树中所有元素都处于被视为已加载状态中时,将首先在根元素上引
发 Loaded 事件。然后在每个子级元素上连续引发 Loaded 事件。 

构建于元素的通用生存期事件上的为以下应用程序模型元素: Application、Window、Page、NavigationWindow 和 Frame。这
些元素使用与其特定用途关联的附加事件扩展了通用生存期事件 
路由事件的处理程序可以在事件数据内将事件标记为已处理。处理事件将有效地缩短路由。类处理是一个编程概念,受路由事件支持。
类处理程序有机会在类级别使用处理程序处理特定路由事件,该处理程序在类的任何实例上的任何实例处理程序之前调用 
另一个要考虑“已处理”问题的情形是,如果您的代码以重要且相对完整的方式响应路由事件,则您通常应将路由事件标记为已处理 

AddHandler(RoutedEvent, Delegate, Boolean)),在某些情况下,控件本身会将某些路由事件标记为已处理。已处理的路由事件
代表 WPF 控件作者这样的决定:即响应路由事件的控件操作是重要的,或者作为控件实现的一部分已完成,事件无需进一步处理。
通常,通过为事件添加一个类处理程序,或重写存在于基类上的虚拟类处理程序之一,可以完成此操作 
隧道路由事件和冒泡路由事件在技术层面上是单独的事件,但是它们有意共享相同的事件数据实例以实现此行为 
隧道路由事件与冒泡路由事件之间的连接是由给定的任意 WPF 类引发自己的已声明路由事件的方式的内部实现来完成的,对于成对
的输入路由事件也是如此。但是除非这一类级实现存在,否则共享命名方案的隧道路由事件与冒泡路由事件之间将没有连接:没有上
述实现,它们将是两个完全独立的路由事件,不会顺次引发,也不会共享事件数据。 
路由事件的类处理主要是用于输入事件和复合控件 
当您通常处理预览事件时,应谨慎地在事件数据中将事件标记为已处理。在引发预览事件的元素(在事件数据中报告为源的元素)之
外的任何元素上处理该事件都有这样的后果:使得元素没有机会处理源自于它的事件。有时这是希望的结果,尤其当该元素存在于控
件的复合关系内时。 
特别是对于输入事件而言,预览事件还与对等的冒泡事件共享事件数据实例。如果您使用预览事件类处理程序将输入事件标记为已处
理,将不会调用冒泡输入事件类处理程序。或者,如果您使用预览事件实例处理程序将事件标记为已处理,则通常不会调用冒泡事件
的处理程序。 
通常使用预览事件的一种情形是复合控件的输入事件处理。 

RoutedPropertyChanged 事件 

某些事件使用显式用于属性更改事件的事件数据类型和委托。该事件数据类型是 RoutedPropertyChangedEventArgs<(Of 
<(T>)>),委托是 RoutedPropertyChangedEventHandler<(Of <(T>)>)。事件数据和委托都具有用于在您定义处理程序时指


定更改属性的实际类型的泛型参数。事件数据包含两个属性,即 OldValue 和 NewValue,之后它们均作为事件数据中的类型参数传
递。 

名称中的“Routed”部分表示属性更改事件注册为一个路由事件。路由属性更改事件的好处是,如果子元素(控件的组成部分)的属性
值更改,控件的顶级也可以接收到属性更改事件。例如,您可能创建一个合并 RangeBase 控件(如 Slider)的控件。如果滑块部分
的 Value 属性值更改,则您可能需要在父控件(而不是在该部分)处理此更改。 

RoutedPropertyChanged 事件 

某些事件使用显式用于属性更改事件的事件数据类型和委托。该事件数据类型是 RoutedPropertyChangedEventArgs<(Of 
<(T>)>),委托是 RoutedPropertyChangedEventHandler<(Of <(T>)>)。事件数据和委托都具有用于在您定义处理程序时指
定更改属性的实际类型的泛型参数。事件数据包含两个属性,即 OldValue 和 NewValue,之后它们均作为事件数据中的类型参数传
递。 

DependencyPropertyChanged 事件 

属于属性更改事件方案一部分的另一对类型是 DependencyPropertyChangedEventArgs 和 
DependencyPropertyChangedEventHandler。这些属性更改的事件不会路由;它们是标准的 CLR 事件。
DependencyPropertyChangedEventArgs 不是普通的事件数据报告类型,因为它不是派生自 EventArgs;
DependencyPropertyChangedEventArgs 是一个结构,而不是一个类。 
与属性更改事件密切相关的一个概念是属性触发器。属性触发器是在样式或模板内部创建的,使用它,您可以创建基于分配有属性触
发器的属性的值的条件行为。属性触发器的属性必须是一个依赖项属性。属性的主要方案是报告控件状态,可能与实时 UI 具有因果
关系,并因此是一个属性触发器候选项。 

其中的一些属性还具有专用属性更改事件。例如,属性 IsMouseCaptured 具有一个属性更改事件 IsMouseCapturedChanged。
该属性本身是只读的,其值由输入系统调整,并且输入系统对每次实时更改都引发 IsMouseCapturedChanged。 

为了补偿具有各种可能值的属性触发器的“if”条件,通常最好使用 Setter 将该属性值设置为默认值。这样,当触发器条件为 true 时,
Trigger 包含的 setter 将具有优先权,而只要触发器条件为 false,则不在 Trigger 内部的 Setter 就具有优先权。 
属性触发器通常适合于一个或多个外观属性应基于同一元素的其他属性的状态而更改的情况。 
如何实现 WeakEvent 模式? 
实现 WeakEvent 模式由三个方面组成: 

. 从 WeakEventManager 类派生一个管理器。 
. 在任何想要注册弱事件的侦听器的类上实现 IWeakEventListener 接口,而不生成源的强引用。 
. 注册侦听器时,对于想要侦听器使用该模式的事件,不要使用该事件的常规的 add 和 remove 访问器,请在该事件的专
WPF) 中的元素以元素树结构形式排列。父元素可以参与处理最初由元素树中的子元素引发的事件,这是由于存在事件路由。
用 WeakEventManager 中改用“AddListener”和“RemoveListener”实现。 


下面的示例使用 XAML 属性语法向公用的父元素(在本示例中为 StackPanel)附加事件处理程序。本示例使用属性语法向 
StackPanel 父元素附加事件处理程序,而不是为每个 Button 子元素都附加一个事件处理程序。这个事件处理模式演示了如何使用
事件路由技术来减少需要附加处理程序的元素数量。每个 Button 的所有冒泡事件都通过父元素进行路由。 

若要使您的自定义事件支持事件路由,需要使用 RegisterRoutedEvent 方法注册 RoutedEvent。本示例演示创建自定义路由事件
的基本原理。 
自定义路事件: public partial class mySimpleButton : Button 
{ 
//public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(); 


 public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent("Tap", 
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(mySimpleButton)); 
public event RoutedEventHandler Tap 
{ 
add { AddHandler(TapEvent, value); } 
remove { RemoveHandler(TapEvent, value); } 
} 
public void RaiseTapEvent() 
{ 
RoutedEventArgs neweEventArgs = new RoutedEventArgs(mySimpleButton.TapEvent); 
RaiseEvent(neweEventArgs); 
} 
protected override void OnClick() 
{ 
RaiseTapEvent(); 
} 
} 
WPF) 子系统为输入提供了一个全新的功能强大的 API。命令是 (WPF) 中的输入机制,它提供的输入处理比设备输入具有更高
的语义级别,(WPF) 子系统提供了一个功能强大的 API,用于获取来自各种设备(包括鼠标、键盘和手写笔)的输入。 

。许多输入事件都有一对与其关联的事件。 例如,键按下事件与 KeyDown 和 PreviewKeyDown 事件关联。 这些事件之间
的差别是它们路由到目标元素的方式。 预览事件在元素树中从根元素到目标元素向下进行隧道操作。 冒泡事件从目标元素到根元素
向上进行冒泡操作。 

下面的示例使用 GetKeyStates 方法确定 Key 是否处于按下状态。 
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0) 
{ 
btnNone.Background = Brushes.Red; 
} 

下面的示例确定鼠标上的 LeftButton 是否处于 Pressed 状态。 
if (Mouse.LeftButton == MouseButtonState.Pressed) 
{ 
UpdateSampleResults("Left Button Pressed"); 
} 
路由事件使用三种路由机制之一:直接、冒泡和隧道。 在直接路由中,源元素是唯一得到通知的元素,该事件不会路由到任何
其他元素。但是,相对于标准 CLR 事件,直接路由事件仍提供了一些其他仅对于路由事件才存在的功能。冒泡操作在元素树中向上
进行,首先通知指明了事件来源的第一个元素,然后是父元素,等等。 隧道操作从元素树的根开始,然后向下进行,以原始的源元素
结束。 

由于输入事件在事件路由中向上冒泡,因此不管哪个元素具有键盘焦点,StackPanel 都将接收输入。 TextBox 控件首先得到
通知,而只有在 TextBox 未处理输入时才会调用 OnTextInputKeyDown 处理程序。如果使用 PreviewKeyDown 事件而不是 
KeyDown 事件,则将首先调用 OnTextInputKeyDown 处理程序。 
在此示例中,处理逻辑写入了两次,一次针对 Ctrl+O,另一次针对按钮的单击事件。使用命令,而不是直接处理输入事件,可
简化此过程。 

为了使元素能够获取键盘焦点,Focusable 属性和 IsVisible 属性必须设置为 true。 某些类(如 Panel)默认情况下将 
Focusable 设置为 false;因此,如果您希望该元素能够获取焦点,则必须将此属性设置为 true。 


在 WPF 中,有两个与焦点有关的主要概念:键盘焦点和逻辑焦点。WPF) ,资源,可以通过一种简单的方法来重用通常定义的对象和
值。 

确保在请求资源之前已在资源集合中对该资源进行了定义。如有必要,您可以使用 DynamicResource 标记扩展在运行时引用
资源,这样可以绕过严格的资源引用创建顺序,但应注意这种 DynamicResource 技术会对性能产生一定的负面影响 
<SolidColorBrush x:Key="MyBrush" Color="Gold"/> 
<Style TargetType="Border" x:Key="PageBackground"> 
<Setter Property="Background" Value="Blue"/> 
</Style> 
引用:<Style TargetType="TextBlock" x:Key="Label"> 
<Setter Property="DockPanel.Dock" Value="Right"/> 
<Setter Property="FontSize" Value="8"/> 
<Setter Property="Foreground" Value="{StaticResource MyBrush}"/> 
</Style> 
引用: 
<Style TargetType="Button" x:Key="GelButton" > 
<Setter Property="Margin" Value="1,2,1,2"/> 
<Setter Property="HorizontalAlignment" Value="Left"/> 
<Setter Property="Template"> 
<Setter.Value> 
...... 
</Setter.Value> 
</Setter> 
</Style> 
SystemFonts: 
<Button Margin="10, 10, 5, 5" Grid.Column="0" Grid.Row="3" 
FontSize="{x:Static SystemFonts.IconFontSize}" 
FontWeight="{x:Static SystemFonts.MessageFontWeight}" 
FontFamily="{x:Static SystemFonts.CaptionFontFamily}"> 
SystemFonts 
</Button> 

系统资源将许多系统度量作为资源公开,以帮助开发人员创建与系统设置一致的可视元素。SystemFonts 是一个类,它包含系
统字体值以及绑定到这些值的系统字体资源。例如,CaptionFontFamily 和 CaptionFontFamilyKey。 
系统字体规格可以用作静态或动态资源。如果您希望字体规格在应用程序运行时自动更新,请使用动态资源;否则,请使用静态
资源。 
引用DynamicResource: 
<Style x:Key="SimpleFont" TargetType="{x:Type Button}"> 
<Setter Property = "FontSize" Value= "{DynamicResource {x:Static SystemFonts.IconFontSizeKey}}"/> 
<Setter Property = "FontWeight" Value= "{DynamicResource {x:Static 
SystemFonts.MessageFontWeightKey}}"/> 
<Setter Property = "FontFamily" Value= "{DynamicResource {x:Static 
SystemFonts.CaptionFontFamilyKey}}"/> 
</Style> 

系统资源会将多个基于系统的设置作为资源进行显示,以帮助您创建与系统设置协调一致的视觉效果。SystemParameters 是一个
类,其中既包含系统参数值属性,又包含绑定到这些值的资源键。例如,FullPrimaryScreenHeight 是 SystemParameters 属性
值,FullPrimaryScreenHeightKey 是相应的资源键。 


在 XAML 中,可以使用 SystemParameters 的成员作为静态属性用法或动态资源引用(静态属性值为资源键)。如果您希望基于
系统的值在应用程序运行时自动更新,请使用动态资源引用;否则请使用静态引用。资源键的属性名称后面附有 Key 后缀。 
<Style x:Key="SimpleParam" TargetType="{x:Type Button}"> 
<Setter Property = "Height" Value= "{DynamicResource {x:Static 
SystemParameters.CaptionHeightKey}}"/> 
<Setter Property = "Width" Value= "{DynamicResource {x:Static 
SystemParameters.IconGridWidthKey}}"/> 
</Style> 
查找template 中资源: 
<Style TargetType="{x:Type Button}"> 
<Setter Property="Template"> 
<Setter.Value> 
<ControlTemplate TargetType="{x:Type Button}"> 
<Grid Margin="5" Name="grid"> 
<Ellipse Stroke="DarkBlue" StrokeThickness="2"> 
<Ellipse.Fill> 
<RadialGradientBrush Center="0.3,0.2" RadiusX="0.5" RadiusY="0.5"> 
<GradientStop Color="Azure" Offset="0.1" /> 
<GradientStop Color="CornflowerBlue" Offset="1.1" /> 
</RadialGradientBrush> 
</Ellipse.Fill> 
</Ellipse> 
<ContentPresenter Name="content" Margin="10" 
HorizontalAlignment="Center" VerticalAlignment="Center"/> 
</Grid> 
</ControlTemplate> 
</Setter.Value> 
</Setter> 
</Style> 
View Code

猜你喜欢

转载自www.cnblogs.com/blogpro/p/11463099.html