精华内容
下载资源
问答
  • WPF教程(一) WPF什么

    万次阅读 多人点赞 2016-09-20 22:07:50
    什么是GUI框架呢?GUI是图形用户界面(Graphical User Interface),这是什么东西呢,也许你现在正盯着它看呢。Windows窗口就有一个GUI,而你读这篇文章的浏览器也有一个GUI来使你上网。 GUI框架允许你用各种GUI...

    WPF,即Windows Presentation Foundation,是微软基于.NET框架下最新的GUI方法。

    那什么是GUI框架呢?GUI是指图形用户界面(Graphical User Interface),这是什么东西呢,也许你现在正盯着它看呢。Windows窗口就有一个GUI,而你读这篇文章的浏览器也有一个GUI来使你上网。

    GUI框架允许你用各种GUI元素(包括标签、文本框以及其他常见的元素)创建一个应用。如果没有GUI框架,你就不得不自己动手来画这些元素了,你还得处理像文本和鼠标输入这种用户交互场景。这是相当巨大的工作量,因此,很多开发者会使用一个GUI框架,不需要关心这些基本任务,把注意力全部集中在开发高级应用。

    GUI框架非常多,在.NET里,最热门的当属WinForms和WPF。WPF算是最新的,而WinForms也继续被微软维护和支持。这两个框架有很多不同的地方,在后面的章节会讲到,但是,它们的目的是一致的:为了更好的使用GUI创建应用。

    在下一章,我们将比较WinForms和WPF的区别。


    展开全文
  • wpf和winform的区别:1、WPF是微软推出取代winform的产品,能分离界面设计人员与开发人员,而WinForm创建丰富的基于Windows的应用程序;2、WPF底层使用的DirectX,winform底层使用的是GDI+。wpf和winform的区别:WPF...

    wpf和winform的区别:1、WPF是微软推出取代winform的产品,能分离界面设计人员与开发人员,而WinForm创建丰富的基于Windows的应用程序;2、WPF底层使用的DirectX,winform底层使用的是GDI+。

    ce5e5a562e0cd0fe3183b9e3df1f320b.png

    wpf和winform的区别:

    WPF,即windows presentation foundation,windows呈现基础,属于.net framework3.0,是微软推出取代winform的产品,能做到分离界面设计人员与开发人员的工作,提供多媒体交互用户图形界面,三大核心程序集是presentationcore、presentationFramework、windowsBase。

    WinForm是·Net开发平台中对Windows Form的一种称谓。Windows窗体可用于设计窗体和可视控件,以创建丰富的基于Windows的应用程序。Windows窗体提供了一套丰富的控件,并且开发人员可以定义自己有特色的新的控件。WinForm控件是指以输入或操作数据的对象。比如:ComponentOne是.net平台下对数据和方法的封装。有自己的属性和方法。属性是控件数据的简单访问者。方法则是控件的一些简单而可见的功能。包含在 .NET Framework 中的 Windows窗体类旨在用于 GUI 开发。

    WPF和winform最大的区别在于WPF底层使用的DirectX,winform底层使用的是GDI+,所以WPF的图形界面上更胜一筹。

    GDI+(Graphics Device Interface)图形设备接口,它的主要任务是负责绘图程序之间的信息交换、处理,所有windows程序的图形输出。

    DirectX(Direct Extension)多媒体编程接口,加强3D图形和声音效果,有很多API组成。按照性质分类可分为四大部分:显示部分,声音部分,输入部分和网络部分。

    展开全文
  • wcf 与wpf什么区别 一个是winform 一个是webform吗
  • WPF 中,如果是鼠标点击拖动窗口坐标,可以调用 Window 的 DragMove 方法,但是如果是触摸,就需要自己调用 Win32 的方法实现 在 WPF 中,调用 Window 的 DragMove 方法要求鼠标的左键(主键)按下,否则将会...

    在 WPF 中,如果是鼠标点击拖动窗口坐标,可以调用 Window 的 DragMove 方法,但是如果是触摸,就需要自己调用 Win32 的方法实现

    在 WPF 中,调用 Window 的 DragMove 方法要求鼠标的左键(主键)按下,否则将会抛出如下代码

    System.InvalidOperationException:“只能在按下主鼠标按钮时调用 DragMove。”

    或英文版的代码

    System.InvalidOperationException:"Can only call DragMove when primary mouse button is down"

    因此想要在 WPF 中使用手指 finger 进行 Touch 触摸拖拽窗口,拖动修改窗口坐标就需要用到 Win32 的方法了。相信大家都知道,在修改某个容器的坐标的时候,不能使用这个容器内的坐标做参考,所以在 Touch 拖动修改窗口坐标的时候,就不能使用监听窗口的事件拿到的坐标来作为参考

    想要能平滑的移动窗口,就需要获取相对于屏幕的坐标,而如果此时处理多指的 Manipulation 的动作,那么整个逻辑将会非常复杂。本文仅仅支持使用一个手指的移动,因为使用了 GetCursorPos 的方法

    当然了,此时假装是支持多指拖动也是可以的,只需要在进行多指触摸的时候开启拖动就可以了,此时用户的交互上不会有很大的差别

    在开始之前,咱来封装一个类 DragMoveWindowHelper 用来在触摸下拖动窗口

        public static class DragMoveWindowHelper
        {
            public static void DragMove(Window window)
            {
            	// 这里的 DragMoveMode 在下文实现
                var dragMoveMode = new DragMoveMode(window);
                dragMoveMode.Start();
            }
        }

    上面代码的 DragMoveMode 类放在下文实现。在封装完成了 DragMoveWindowHelper 类就可以尝试在拖动的时候使用,如下面代码

        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                TouchDown += MainWindow_TouchDown;
                TouchUp += MainWindow_TouchUp;
            }
    
            private void MainWindow_TouchUp(object sender, TouchEventArgs e)
            {
                _currentTouchCount--;
            }
    
            private void MainWindow_TouchDown(object sender, TouchEventArgs e)
            {
                CaptureTouch(e.TouchDevice);
    
                if (_currentTouchCount == 0)
                {
                    DragMoveWindowHelper.DragMove(this);
                }
    
                _currentTouchCount++;
            }
    
            private uint _currentTouchCount;
        }

    上面代码有一点需要小心就是 CaptureTouch 是必备的,否则你会发现拖动的时候,拖动太快了,就丢失触摸设备了,触摸设备被你窗口后面的其他软件抓了

    下面开始实现 DragMoveMode 也就是核心的通过触摸拖动窗口的逻辑

    大概对外的接口方法实现请看代码

            class DragMoveMode
            {
                public DragMoveMode(Window window)
                {
                    _window = window;
                }
    
                public void Start()
                {
                    var window = _window;
    
                    window.PreviewMouseMove += Window_PreviewMouseMove;
                    window.PreviewMouseUp += Window_PreviewMouseUp;
                    window.LostMouseCapture += Window_LostMouseCapture;
                }
    
                public void Stop()
                {
                    Window window = _window;
    
                    window.PreviewMouseMove -= Window_PreviewMouseMove;
                    window.PreviewMouseUp -= Window_PreviewMouseUp;
                    window.LostMouseCapture -= Window_LostMouseCapture;
                }
    
                private readonly Window _window;
            }

    在上面代码里面监听 PreviewMouseMove 是为了获取移动的时机,而不是为了获取相对的坐标。而 PreviewMouseUp 可以用来了解啥时候结束。当然了 LostMouseCapture 也需要监听,和 PreviewMouseUp 一样用来了解啥时候结束

    在 Window_PreviewMouseMove 方法需要先判断是否第一次进入移动,因此咱没有监听 MouseDown 方法。为什么没有监听 MouseDown 方法,是因为在上层业务此时业务调用 MoseDown 完成

    判断是否第一次进入移动需要一个辅助的字段,咱定义一个叫上一次点击的坐标字段

                private Win32.User32.Point? _lastPoint;

    上面代码的 Win32.User32 是我定义的代码,这些定义将会放在本文最后

    判断是第一次进入移动可以使用下面代码

                private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
                {
                    Win32.User32.GetCursorPos(out var lpPoint);
    
                    if (_lastPoint == null)
                    {
                        _lastPoint = lpPoint;
                        _window.CaptureMouse();
                    }
                }

    通过 GetCursorPos 的 Win32 方法可以拿到相对于屏幕坐标的鼠标坐标,而触摸默认会将第一个触摸点转换为鼠标坐标,因此拿到的坐标点不是相对于窗口内的,这样就能做到在移动的时候不会抖

    接下来判断相对上一次的移动距离,如下面代码

                    var dx = lpPoint.X - _lastPoint.Value.X;
                    var dy = lpPoint.Y - _lastPoint.Value.Y;
    
                    Debug.WriteLine($"dx={dx} dy={dy}");

    拿到的 dx 和 dy 就可以用来设置窗口的左上角坐标了。而此时不能通过 Window 的 Top 和 Left 属性获取,这两个属性的值使用的是 WPF 单位和坐标,而咱计算的 dx 和 dy 是相对于屏幕的坐标,因此需要调用 GetWindowRect 这个 win32 方法获取窗口所在屏幕的坐标

    设置窗口坐标也需要使用屏幕坐标来设置,需要调用 SetWindowPos 方法,代码如下

         var handle = new WindowInteropHelper(_window).Handle;
    
         Win32.User32.GetWindowRect(handle, out var lpRect);
    
         Win32.User32.SetWindowPos(handle, IntPtr.Zero, lpRect.Left + dx, lpRect.Top + dy, 0, 0,
                            (int) (Win32.User32.WindowPositionFlags.SWP_NOSIZE |
                                   Win32.User32.WindowPositionFlags.SWP_NOZORDER));

    这个 Window_PreviewMouseMove 方法代码如下

                private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
                {
                    Win32.User32.GetCursorPos(out var lpPoint);
    
                    if (_lastPoint == null)
                    {
                        _lastPoint = lpPoint;
                        _window.CaptureMouse();
                    }
    
                    var dx = lpPoint.X - _lastPoint.Value.X;
                    var dy = lpPoint.Y - _lastPoint.Value.Y;
    
                    Debug.WriteLine($"dx={dx} dy={dy}");
    
                    // 以下的 60 是表示最大移动速度
                    if (Math.Abs(dx) < 60 && Math.Abs(dy) < 60)
                    {
                        var handle = new WindowInteropHelper(_window).Handle;
    
                        Win32.User32.GetWindowRect(handle, out var lpRect);
    
                        Win32.User32.SetWindowPos(handle, IntPtr.Zero, lpRect.Left + dx, lpRect.Top + dy, 0, 0,
                            (int) (Win32.User32.WindowPositionFlags.SWP_NOSIZE |
                                   Win32.User32.WindowPositionFlags.SWP_NOZORDER));
                    }
    
                    _lastPoint = lpPoint;
                }
    

    在 Window_PreviewMouseUp 和 Window_LostMouseCapture 方法调用的是清理的代码,解决内存泄露

                private void Window_LostMouseCapture(object sender, MouseEventArgs e)
                {
                    Stop();
                }
    
                private void Window_PreviewMouseUp(object sender, MouseButtonEventArgs e)
                {
                    Stop();
                }

    大概就完成了触摸拖动窗口的逻辑,下面代码是 Win32 的代码,需要加到你的代码里面,这样才能构建通过

            private static class Win32
            {
                public static class User32
                {
                    /// <summary>
                    /// 改变一个子窗口、弹出式窗口和顶层窗口的尺寸、位置和 Z 序。
                    /// </summary>
                    /// <param name="hWnd">窗口句柄。</param>
                    /// <param name="hWndInsertAfter">
                    /// 在z序中的位于被置位的窗口前的窗口句柄。该参数必须为一个窗口句柄,或下列值之一:
                    /// <para>HWND_BOTTOM:将窗口置于 Z 序的底部。如果参数hWnd标识了一个顶层窗口,则窗口失去顶级位置,并且被置在其他窗口的底部。</para>
                    /// <para>HWND_NOTOPMOST:将窗口置于所有非顶层窗口之上(即在所有顶层窗口之后)。如果窗口已经是非顶层窗口则该标志不起作用。</para>
                    /// <para>HWND_TOP:将窗口置于Z序的顶部。</para>
                    /// <para>HWND_TOPMOST:将窗口置于所有非顶层窗口之上。即使窗口未被激活窗口也将保持顶级位置。</para>
                    /// </param>
                    /// <param name="x">以客户坐标指定窗口新位置的左边界。</param>
                    /// <param name="y">以客户坐标指定窗口新位置的顶边界。</param>
                    /// <param name="cx">以像素指定窗口的新的宽度。</param>
                    /// <param name="cy">以像素指定窗口的新的高度。</param>
                    /// <param name="wFlagslong">
                    /// 窗口尺寸和定位的标志。该参数可以是下列值的组合:
                    /// <para>SWP_ASYNCWINDOWPOS:如果调用进程不拥有窗口,系统会向拥有窗口的线程发出需求。这就防止调用线程在其他线程处理需求的时候发生死锁。</para>
                    /// <para>SWP_DEFERERASE:防止产生 WM_SYNCPAINT 消息。</para>
                    /// <para>SWP_DRAWFRAME:在窗口周围画一个边框(定义在窗口类描述中)。</para>
                    /// <para>SWP_FRAMECHANGED:给窗口发送 WM_NCCALCSIZE 消息,即使窗口尺寸没有改变也会发送该消息。如果未指定这个标志,只有在改变了窗口尺寸时才发送 WM_NCCALCSIZE。</para>
                    /// <para>SWP_HIDEWINDOW:隐藏窗口。</para>
                    /// <para>SWP_NOACTIVATE:不激活窗口。如果未设置标志,则窗口被激活,并被设置到其他最高级窗口或非最高级组的顶部(根据参数hWndlnsertAfter设置)。</para>
                    /// <para>SWP_NOCOPYBITS:清除客户区的所有内容。如果未设置该标志,客户区的有效内容被保存并且在窗口尺寸更新和重定位后拷贝回客户区。</para>
                    /// <para>SWP_NOMOVE:维持当前位置(忽略X和Y参数)。</para>
                    /// <para>SWP_NOOWNERZORDER:不改变 Z 序中的所有者窗口的位置。</para>
                    /// <para>SWP_NOREDRAW:不重画改变的内容。如果设置了这个标志,则不发生任何重画动作。适用于客户区和非客户区(包括标题栏和滚动条)和任何由于窗回移动而露出的父窗口的所有部分。如果设置了这个标志,应用程序必须明确地使窗口无效并区重画窗口的任何部分和父窗口需要重画的部分。</para>
                    /// <para>SWP_NOREPOSITION:与 SWP_NOOWNERZORDER 标志相同。</para>
                    /// <para>SWP_NOSENDCHANGING:防止窗口接收 WM_WINDOWPOSCHANGING 消息。</para>
                    /// <para>SWP_NOSIZE:维持当前尺寸(忽略 cx 和 cy 参数)。</para>
                    /// <para>SWP_NOZORDER:维持当前 Z 序(忽略 hWndlnsertAfter 参数)。</para>
                    /// <para>SWP_SHOWWINDOW:显示窗口。</para>
                    /// </param>
                    /// <returns>如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误消息,请调用 GetLastError 函数。</returns>
                    [DllImport(LibraryName, ExactSpelling = true, SetLastError = true)]
                    public static extern Int32 SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, Int32 x, Int32 y, Int32 cx,
                        Int32 cy, Int32 wFlagslong);
    
                    [Flags]
                    public enum WindowPositionFlags
                    {
                        /// <summary>
                        ///     If the calling thread and the thread that owns the window are attached to different input queues, the system posts
                        ///     the request to the thread that owns the window. This prevents the calling thread from blocking its execution while
                        ///     other threads process the request.
                        /// </summary>
                        SWP_ASYNCWINDOWPOS = 0x4000,
    
                        /// <summary>
                        ///     Prevents generation of the WM_SYNCPAINT message.
                        /// </summary>
                        SWP_DEFERERASE = 0x2000,
    
                        /// <summary>
                        ///     Draws a frame (defined in the window's class description) around the window.
                        /// </summary>
                        SWP_DRAWFRAME = 0x0020,
    
                        /// <summary>
                        ///     Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to the window, even if
                        ///     the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE is sent only when the window's
                        ///     size is being changed.
                        /// </summary>
                        SWP_FRAMECHANGED = 0x0020,
    
                        /// <summary>
                        ///     Hides the window.
                        /// </summary>
                        SWP_HIDEWINDOW = 0x0080,
    
                        /// <summary>
                        ///     Does not activate the window. If this flag is not set, the window is activated and moved to the top of either the
                        ///     topmost or non-topmost group (depending on the setting of the hWndInsertAfter parameter).
                        /// </summary>
                        SWP_NOACTIVATE = 0x0010,
    
                        /// <summary>
                        ///     Discards the entire contents of the client area. If this flag is not specified, the valid contents of the client
                        ///     area are saved and copied back into the client area after the window is sized or repositioned.
                        /// </summary>
                        SWP_NOCOPYBITS = 0x0100,
    
                        /// <summary>
                        ///     Retains the current position (ignores X and Y parameters).
                        /// </summary>
                        SWP_NOMOVE = 0x0002,
    
                        /// <summary>
                        ///     Does not change the owner window's position in the Z order.
                        /// </summary>
                        SWP_NOOWNERZORDER = 0x0200,
    
                        /// <summary>
                        ///     Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to the client area,
                        ///     the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a
                        ///     result of the window being moved. When this flag is set, the application must explicitly invalidate or redraw any
                        ///     parts of the window and parent window that need redrawing.
                        /// </summary>
                        SWP_NOREDRAW = 0x0008,
    
                        /// <summary>
                        ///     Same as the SWP_NOOWNERZORDER flag.
                        /// </summary>
                        SWP_NOREPOSITION = 0x0200,
    
                        /// <summary>
                        ///     Prevents the window from receiving the WM_WINDOWPOSCHANGING message.
                        /// </summary>
                        SWP_NOSENDCHANGING = 0x0400,
    
                        /// <summary>
                        ///     Retains the current size (ignores the cx and cy parameters).
                        /// </summary>
                        SWP_NOSIZE = 0x0001,
    
                        /// <summary>
                        ///     Retains the current Z order (ignores the hWndInsertAfter parameter).
                        /// </summary>
                        SWP_NOZORDER = 0x0004,
    
                        /// <summary>
                        ///     Displays the window.
                        /// </summary>
                        SWP_SHOWWINDOW = 0x0040
                    }
    
    
                    public const string LibraryName = "user32";
    
                    /// <summary>
                    /// 获取的是以屏幕为坐标轴窗口坐标
                    /// </summary>
                    /// <param name="hWnd"></param>
                    /// <param name="lpRect"></param>
                    /// <returns></returns>
                    [return: MarshalAs(UnmanagedType.Bool)]
                    [DllImport(LibraryName, ExactSpelling = true)]
                    public static extern bool GetWindowRect(IntPtr hWnd, out Rectangle lpRect);
    
                    /// <summary>
                    /// 在 Win32 函数使用的矩形
                    /// </summary>
                    [StructLayout(LayoutKind.Sequential)]
                    public partial struct Rectangle : IEquatable<Rectangle>
                    {
                        /// <summary>
                        ///  创建在 Win32 函数使用的矩形
                        /// </summary>
                        /// <param name="left"></param>
                        /// <param name="top"></param>
                        /// <param name="right"></param>
                        /// <param name="bottom"></param>
                        public Rectangle(int left = 0, int top = 0, int right = 0, int bottom = 0)
                        {
                            Left = left;
                            Top = top;
                            Right = right;
                            Bottom = bottom;
                        }
    
                        /// <summary>
                        /// 创建在 Win32 函数使用的矩形
                        /// </summary>
                        /// <param name="width">矩形的宽度</param>
                        /// <param name="height">矩形的高度</param>
                        public Rectangle(int width = 0, int height = 0) : this(0, 0, width, height)
                        {
                        }
    
                        public int Left;
                        public int Top;
                        public int Right;
                        public int Bottom;
    
                        public bool Equals(Rectangle other)
                        {
                            return (Left == other.Left) && (Right == other.Right) && (Top == other.Top) &&
                                   (Bottom == other.Bottom);
                        }
    
                        public override bool Equals(object obj)
                        {
                            return obj is Rectangle && Equals((Rectangle) obj);
                        }
    
                        public static bool operator ==(Rectangle left, Rectangle right)
                        {
                            return left.Equals(right);
                        }
    
                        public static bool operator !=(Rectangle left, Rectangle right)
                        {
                            return !(left == right);
                        }
    
                        public override int GetHashCode()
                        {
                            unchecked
                            {
                                var hashCode = (int) Left;
                                hashCode = (hashCode * 397) ^ (int) Top;
                                hashCode = (hashCode * 397) ^ (int) Right;
                                hashCode = (hashCode * 397) ^ (int) Bottom;
                                return hashCode;
                            }
                        }
    
                        /// <summary>
                        /// 获取当前矩形是否空矩形
                        /// </summary>
                        public bool IsEmpty => this.Left == 0 && this.Top == 0 && this.Right == 0 && this.Bottom == 0;
    
                        /// <summary>
                        /// 矩形的宽度
                        /// </summary>
                        public int Width
                        {
                            get { return unchecked((int) (Right - Left)); }
                            set { Right = unchecked((int) (Left + value)); }
                        }
    
                        /// <summary>
                        /// 矩形的高度
                        /// </summary>
                        public int Height
                        {
                            get { return unchecked((int) (Bottom - Top)); }
                            set { Bottom = unchecked((int) (Top + value)); }
                        }
    
                        /// <summary>
                        /// 通过 x、y 坐标和宽度高度创建矩形
                        /// </summary>
                        /// <param name="x"></param>
                        /// <param name="y"></param>
                        /// <param name="width"></param>
                        /// <param name="height"></param>
                        /// <returns></returns>
                        public static Rectangle Create(int x, int y, int width, int height)
                        {
                            unchecked
                            {
                                return new Rectangle(x, y, (int) (width + x), (int) (height + y));
                            }
                        }
    
                        public static Rectangle From(ref Rectangle lvalue, ref Rectangle rvalue,
                            Func<int, int, int> leftTopOperation,
                            Func<int, int, int> rightBottomOperation = null)
                        {
                            if (rightBottomOperation == null)
                                rightBottomOperation = leftTopOperation;
                            return new Rectangle(
                                leftTopOperation(lvalue.Left, rvalue.Left),
                                leftTopOperation(lvalue.Top, rvalue.Top),
                                rightBottomOperation(lvalue.Right, rvalue.Right),
                                rightBottomOperation(lvalue.Bottom, rvalue.Bottom)
                            );
                        }
    
                        public void Add(Rectangle value)
                        {
                            Add(ref this, ref value);
                        }
    
                        public void Subtract(Rectangle value)
                        {
                            Subtract(ref this, ref value);
                        }
    
                        public void Multiply(Rectangle value)
                        {
                            Multiply(ref this, ref value);
                        }
    
                        public void Divide(Rectangle value)
                        {
                            Divide(ref this, ref value);
                        }
    
                        public void Deflate(Rectangle value)
                        {
                            Deflate(ref this, ref value);
                        }
    
                        public void Inflate(Rectangle value)
                        {
                            Inflate(ref this, ref value);
                        }
    
                        public void Offset(int x, int y)
                        {
                            Offset(ref this, x, y);
                        }
    
                        public void OffsetTo(int x, int y)
                        {
                            OffsetTo(ref this, x, y);
                        }
    
                        public void Scale(int x, int y)
                        {
                            Scale(ref this, x, y);
                        }
    
                        public void ScaleTo(int x, int y)
                        {
                            ScaleTo(ref this, x, y);
                        }
    
                        public static void Add(ref Rectangle lvalue, ref Rectangle rvalue)
                        {
                            lvalue.Left += rvalue.Left;
                            lvalue.Top += rvalue.Top;
                            lvalue.Right += rvalue.Right;
                            lvalue.Bottom += rvalue.Bottom;
                        }
    
                        public static void Subtract(ref Rectangle lvalue, ref Rectangle rvalue)
                        {
                            lvalue.Left -= rvalue.Left;
                            lvalue.Top -= rvalue.Top;
                            lvalue.Right -= rvalue.Right;
                            lvalue.Bottom -= rvalue.Bottom;
                        }
    
                        public static void Multiply(ref Rectangle lvalue, ref Rectangle rvalue)
                        {
                            lvalue.Left *= rvalue.Left;
                            lvalue.Top *= rvalue.Top;
                            lvalue.Right *= rvalue.Right;
                            lvalue.Bottom *= rvalue.Bottom;
                        }
    
                        public static void Divide(ref Rectangle lvalue, ref Rectangle rvalue)
                        {
                            lvalue.Left /= rvalue.Left;
                            lvalue.Top /= rvalue.Top;
                            lvalue.Right /= rvalue.Right;
                            lvalue.Bottom /= rvalue.Bottom;
                        }
    
                        public static void Deflate(ref Rectangle target, ref Rectangle deflation)
                        {
                            target.Top += deflation.Top;
                            target.Left += deflation.Left;
                            target.Bottom -= deflation.Bottom;
                            target.Right -= deflation.Right;
                        }
    
                        public static void Inflate(ref Rectangle target, ref Rectangle inflation)
                        {
                            target.Top -= inflation.Top;
                            target.Left -= inflation.Left;
                            target.Bottom += inflation.Bottom;
                            target.Right += inflation.Right;
                        }
    
                        public static void Offset(ref Rectangle target, int x, int y)
                        {
                            target.Top += y;
                            target.Left += x;
                            target.Bottom += y;
                            target.Right += x;
                        }
    
                        public static void OffsetTo(ref Rectangle target, int x, int y)
                        {
                            var width = target.Width;
                            var height = target.Height;
                            target.Left = x;
                            target.Top = y;
                            target.Right = width;
                            target.Bottom = height;
                        }
    
                        public static void Scale(ref Rectangle target, int x, int y)
                        {
                            target.Top *= y;
                            target.Left *= x;
                            target.Bottom *= y;
                            target.Right *= x;
                        }
    
                        public static void ScaleTo(ref Rectangle target, int x, int y)
                        {
                            unchecked
                            {
                                x = (int) (target.Left / x);
                                y = (int) (target.Top / y);
                            }
    
                            Scale(ref target, x, y);
                        }
                    }
    
                    [DllImport(LibraryName, SetLastError = true)]
                    [return: MarshalAs(UnmanagedType.Bool)]
                    public static extern bool GetCursorPos(out Point lpPoint);
    
                    [StructLayout(LayoutKind.Sequential)]
                    public struct Point
                    {
                        public int X;
                        public int Y;
    
                        public Point(int x, int y)
                        {
                            this.X = x;
                            this.Y = y;
                        }
    
                        public static implicit operator System.Drawing.Point(Point p)
                        {
                            return new System.Drawing.Point(p.X, p.Y);
                        }
    
                        public static implicit operator Point(System.Drawing.Point p)
                        {
                            return new Point(p.X, p.Y);
                        }
                    }
                }
            }

    如果发现你的代码依然无法构建通过,还请参阅我的测试代码从里面抄代码解决找不到某个类

    本文代码放在github欢迎小伙伴访问

    关于 Win32 方法的定义,我推荐使用官方的 dotnet/pinvoke: A library containing all P/Invoke code so you don't have to import it every time. Maintained and updated to support the latest Windows OS.

    参考

    WPF Touch DragMove() - CodeProject

    [Solved] How to drag window with finger not mouse - CodeProject

    Way to make a Windowless WPF window draggable without getting InvalidOperationException - Stack Overflow

    Can only call DragMove when primary mouse button is down.

    microsoft/XamlBehaviorsWpf: Home for WPF XAML Behaviors on GitHub.

    我搭建了自己的博客 https://blog.lindexi.com/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新

    如果在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎大家加入

    如有不方便在博客评论的问题,可以加我 QQ 2844808902 交流

    知识共享许可协议
    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系

    展开全文
  • 我想问的是这里求的 TextBlock 相对于 Window 的偏移量,是什么意思啊,这样的结果有什么意义呢?那些场合用得上? 如果是坐标的话,可以用UIElement.TranslatePoint 方法啊([url=...
  • WPF学习

    万次阅读 多人点赞 2019-03-05 22:00:17
    首先感谢刘铁锰先生的《深入浅出WPF》,学习WPF过程碰上很多新概念,如Data Binding、路由事件,命令、各种模板等。 WPF:编写CS端的UI技术。 怎么去掉WPF窗体靠上多出黑色的长条?在vs界面的菜单栏点击调试-选项...

    首先感谢刘铁锰先生的《深入浅出WPF》,学习WPF过程碰上很多新概念,如Data Binding、路由事件,命令、各种模板等。

    WPF:编写CS端的UI技术。

    怎么去掉WPF窗体靠上多出黑色的长条?在vs界面的菜单栏点击调试-选项,把启用XAML的UI调试工具勾选去掉即可。(我自己觉得偶尔会用用这个)

    1   认识WPF

    1.1 新建WPF项目

    生成

    Properties:资源   引用:引用其他类库 App.xmal:程序主体(一个GUI进程需要有一个窗体,App.xmal文件的作用声明了程序的进程,同时指定程序的主窗体),点开app.xaml.cs,它是app.xaml的后台代码。MainWindow1.xmal分支:默认程序的主窗体。

     

    1.2 最简单的XAML代码

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            
        </Grid>
    </Window>

    x:Class是当XAML解析器将包含它的标签的解析成C#类的类名。是来自xmlns:x的命名空间。第一行xmlns是默认命名空间。<Window>和<Grid>都来自默认空间。Title是窗体标题,Height是窗体高度,Width是窗体宽度。可以引用CLS的命名空间。

    引用第三方的类库:

    xmlns:common(映射名,自定义)="clr-namespace:Common(类库中名称空间的名字);assembly=MyLibrary(类库名,比如MyLibrary.dll)"
    • xmlns用于在Xaml中声明名称空间的Attribute
    • 冒号的映射名是可选的
    • 引号的字符串确定了哪个类库以及类库哪个名称空间

     

    1.3 XAML文档的树形结构

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="173" Width="296">
        <StackPanel Background="LightBlue">
            <TextBox x:Name='textBox1' Margin="5"></TextBox>
            <TextBox x:Name='textBox2' Margin="5"></TextBox>
            <StackPanel Orientation="Horizontal">
                <TextBox x:Name="textBox3" Width="140" Margin="5"/>
                <TextBox x:Name="textBox4" Width="120" Margin="5"/>
            </StackPanel>
            <Button x:Name="button1" Margin="5">
                <Image Source="C:\Users\14751\Pictures\Camera Roll\1.png" Width="23" Height="23"/>
            </Button>
        </StackPanel>
    </Window>

     

    树形框架结构如下:

    <Window >
        <StackPanel >
            <TextBox ></TextBox>
            <TextBox></TextBox>
            <StackPanel ">
                <TextBox />
                <TextBox />
            </StackPanel>
            <Button>
                <Image />
            </Button>
        </StackPanel>
    </Window>

    StackPanel可以把内部元素在纵向或横向上紧凑排列、形成栈式布局。也可以用Grid完成上面窗体,代码如下。

    <Window x:Class="WPFTest1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFTest1"
            mc:Ignorable="d"
            Title="MainWindow" Height="173" Width="296">
        <Grid Background="LightSlateGray">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="7*"/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="33"/>
                <RowDefinition Height="33"/>
                <RowDefinition Height="33"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
             <TextBox x:Name="textBox1" Grid.Column="0" Grid.Row="0"  Grid.ColumnSpan="2" Margin="5"/>
             <TextBox x:Name="textBox2" Grid.Column="0" Grid.Row="1"  Grid.ColumnSpan="2" Margin="5"/>
             <TextBox x:Name="textBox3" Grid.Column="0" Grid.Row="2"  Grid.ColumnSpan="1" Margin="5"/>
             <TextBox x:Name="textBox4" Grid.Column="1" Grid.Row="2"  Grid.ColumnSpan="1" Margin="5"/>
             <Button x:Name="button5" Grid.Column="0" Grid.Row="3"  Grid.ColumnSpan="2" Margin="5">
                <Image Source="C:\Users\14751\Pictures\Camera Roll\1.png" Width="23" Height="23"/>
            </Button>
        </Grid>
    </Window>
    

    WPF的布局原理:以<Window>对象为根节点,一层层向下包含。

    1.4 使用Attribute为对象属性赋值

     <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Rectangle x:Name="rectangle" Width="200" Height="120" Fill="Blue"/>
     </Grid>

    Rectangle类对象Fill属性的类型是Brush,Brush是个抽象类的单色画刷,实际上编译成:

    SoildColorBrush sBrush=new SoildColorBrush();
    sBrush.Color=Color.Blue;
    this.rectangle.Fill=sBrush;
    
    ......
    

    使用TypeConveter类将XAML标签的Attribute与对象的Property进行映射。

    1.5 径向渐变画刷

     <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Ellipse Width="120" Height="120">
                <Ellipse.Fill>
                    <RadialGradientBrush GradientOrigin="0.25,0.25" RadiusX="0.75" RadiusY="0.75">
                        <RadialGradientBrush.GradientStops>
                            <GradientStop Color="White" Offset="0"/>
                            <GradientStop Color="Black" Offset="0.65"/>
                            <GradientStop Color="Gray" Offset="0.8"/>
                        </RadialGradientBrush.GradientStops>
                    </RadialGradientBrush>
                </Ellipse.Fill>
            </Ellipse>
        </Grid>

    由此得出简化XAML的技巧:

    • Attribute=value优于属性元素
    • 充分利用默认值 StartPoint="0,0" EndPoint="1.1"是默认值,可省略
    • 充分利用XAML简写方式,比如LinearGradientBrush.GradientStops的数据类型GradientStopCollection

    1.6 标记扩展

    标记扩展也是为属性赋值,只不过依赖于其他对象的属性值。尽管很方便,只有MarkupExtension类的派生类才能使用标记扩展语法。

        <StackPanel Background="LightSlateGray">
            <TextBlock Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}" Margin="5"/>
            <Slider x:Name="slider1" Margin="5"/>
        </StackPanel>

    属性元素也可以做到,比较复杂:

    <StackPanel Background="LightSlateGray">
            <TextBox  Margin="5">
                <TextBox.Text>
                    <Binding ElementName="slider1" Path="Value" Mode="OneWay"></Binding>
                </TextBox.Text>
            </TextBox>
            <Slider x:Name="slider1" Margin="5"/>
     </StackPanel>

    1.7 事件

          当一个XAML标签对应着对象时,这个标签的一部分Attribute会对应这个对象的Property。

     <Button Name="button1" Click="button1_Click"></Button>

         在window. xaml.cs后面:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
    
    }

        也可以直接在xaml写点击事件:

    <Grid>
            <Button Name="button1" Click="button1_Click"></Button>
    </Grid>
    
    <x:Code>
       <![CDATA[
            private void button1_Click(object sender, RoutedEventArgs e)
            {
    
            }
       ]]>
    </:Code>
    
    
    

    2 常用属性

    2.1 x:classModifier

    定义了XAML编译由标签编译生成的类具有的访问空指类别。internal与private等价。写internal可能编译报错,把window类的声明,把public改成internal。

    2.2 x:Name

    定义了控件名称,不能相同。

    后台获取:

    xaml代码:
    <TextBox x:Name="textBox" Margin="5">
    
    ------
    c#代码:
    string txtName=textBox.Name;
    

    2.3 x:FieldModifier

       控件的访问级别。默认是internal。

    2.4 x:Key

    <Window.Resources>
         <sys:String x:Key="myString">Hello WPF!</sys:String>
    </Window.Resources>
    <StackPanel>
         <TextBox Text="{StaticResource ResourceKey=myString}" Margin="5"/>
    </StackPanel>
    

        使用String类,用xmlns:sys="clr-namespace:System;assembly=mscorlib"引用了mscorlib.dll,把System名称映射为sys名称空间。

    string str=this.FindResource("myString") as string;
    this.textBox1.Text=str;

    2.5 x:Shared

             把上面的x:Key当作资源放进资源字典后,需要设定检索的对象是同一个对象还是多个副本。

    2.6 x命名空间中的标记扩展

       2.6.1 x:Type

           把某些对象资源放进资源字典里的数据类型。

    mywindow.xaml
    <StackPanel Background="LightBlue">
            <TextBox Margin="5"></TextBox>
            <TextBox Margin="5"></TextBox>
            <TextBox Margin="5"></TextBox>
            <Button Content="OK" Margin="5"/>
    </StackPanel>
    
    MyButton类
     class MyButton:Button
        {
            public Type UserWindowType { get; set; }
            protected override void OnClick()
            {
                base.OnClick();
                Window win = Activator.CreateInstance(this.UserWindowType) as Window;
                if (win != null)
                {
                    win.ShowDialog();
                }
            }
        }
    
    MainWindow.xaml
      <StackPanel>
            <local:MyButton Content="Show" UserWindowType="{x:Type TypeName=local:mywindow}" Margin="5"/>
        </StackPanel>

     2.6.2 x:Null

    显式地对一个属性赋一个空值。

    <Buttob Content="OK" Style="{x:Null}">

    下面一个例子,把一个Style放在window的资源里,并把它的x:Key和TargetType设置了Button类型,除了最后一个Button

    <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
         <Setter Property="Width" Value="60"/>
    </Style>
    
    <StackPanel>
         <Button Content="Ok"/>
         <Button Content="Ok" Style="{x:Null}"/>
    </StackPanel>
    

    也可以写成

     <Button Content="Ok" >
           <Button.Style>
                  <x:Null/>
           </Button.Style>
     </Button>

    2.6.3 x:Array

    在WPF把包含的数据的对象称为数据源,如果把一个x:Array的实例作为数据源提供给一个ListBox。

    <ListBox Margin="5">
       <ListBox.ItemsSource>
             <x:Array Type="sys:String">
                  <sys:String>Tim</sys:String>
                  <sys:String>Tom</sys:String>
                  <sys:String>Victor</sys:String>
             </x:Array>
       </ListBox.ItemsSource>
    </ListBox>
    

    2.6.3 x:Static

    x:Static是一个很常用的标记扩展。功能是在XAML文档中使用数据类型的static成员。

    pubic static string WindowTitle="山高月小";
    
    public static string  ShowText{get{return "水落石出";}}
    
    
    <Window x:Class="Window1">
       -----
       Title="{x:Static local:Window1.WindowTitle}"  Height="120" Width="300">
       <StackPanel>
          <TextBlock FontSize="32" Text="{x:Static local:Window1.ShowText}" />
       </StackPanel>
    
    </Window>
    

    2.7 XAML指定元素

     2.7.1 x:Code

     可以让XAML包含一些本应放置在后置代码的C#代码。

     2.7.2 x:XData

    <Window.Resources>
       <XmlDataProvider x:Key="InventoryData" XPath="Inventory/Books">
           <x:XData>
                 <Supermarket xmlns="">
                       <Fruits>
                            <Fruit Name="Peach"/>
                            <Fruit Name="Banana"/>
                            <Fruit Name="Orange"/>
                       </Fruits>
                       <Drinks>
                            <Drink Name="Coca"/>
                            <Drink Name="PEPSI"/>
                       </Drinks>
                 </Supermarket>
           </x:XData>
       </XmlDataProvider>
    </Window.Resources>
    

     3 控件

      我们把符合某类内容模型的UI元素称为一个族。以下是各种的内容模型。

      3.1 ContentControl

     单一内容控件。共同点:

    • 派生自ContentControl
    • 内容属性的名称为Content
    • 只能由单一元素充当其内容
    <StackPanel>
         <Button Margin="5"> 
              <TextBlock Text="Hello" />
         </Button>
         <Button Margin="5"> 
              <Image Source=".\smile.png" Width="30" Height="30"/>
         </Button>
    </StackPanel>

       想让Button的内容既包含文字又包含图片是不行的。

    ContentControl族包含的控件
    Button ButtonBase CheckBox ComboBoxItem
    ContentControl Frame GridViewColumnHeader GroupItem
    Lable ListBoxItem ListViewItem NavigationWindow
    RadioButton RepeatButton ScrollViewer StatusBarItem
    TpggleButton ToolTip UserControl Window

    3.2 ItemsControl

    • 均派生自ItemsControl类。
    • 用于显示列表化的数据
    • 内容属性为Item或ItemsSource
    • 每种ItemsControl都对应有自己的条目容器(ItemContainer)
    ItemsControl族所包含的控件
    Menu MenuBase ContextMenu ComboBox
    ItemsControl ListBox ListView TabControl
    TreeView Selector StatusBar  
    <Grid>
         <ListBox Margin="5">
              <CheckBox x:Name="checkBoxTim"  Content="Tim"/>
              <CheckBox x:Name="checkBoxTom"  Content="Tom"/>
              <CheckBox x:Name="checkBoxBruce"  Content="Bruce"/>
              <Button x:Name="buttonMess"  Content="Mess"/>
              <Button x:Name="buttonOwen"  Content="Owen"/>
              <Button x:Name="buttonVictor"  Content="Victor"/>
         </ListBox>
    </Grid>
    

    当我们尝试着去寻找窗体或者页面中某个控件的子控件或者父控件的时候,可以用VisualTreeHelper.GetChild()和VisualTreeHelper.GetParent():

    XAML:
    <Button x:Name="buttonVictor" Content="Victor" Click="buttonVictor_Click"/>
    
    C#:
    
    private void buttonVictor_Click(object sender,RoutedEventArgs e)
    {
        Button btn=sender as Button;
        DependencyObject level1=VisualTreeHelper.GetParent(btn);
        DependencyObject level2=VisualTreeHelper.GetParent(level1);
        DependencyObject leve13=VisualTreeHelper.GetParent(level2);
        MessageBox.Show(level3.GetType().ToString());
    }
    

    后台绑定数据时:

    程序添加Employee类:
    public Class Employee
    {
         public int id{get;set;}
         public string Name{get;set;}
         public int Age{get;set;}
    }
    有一个Employee类型的集合:
    List<Employee> empList=new List<Employee>(){
         new Employee(){Id=1,Name="Tim",Age=30},
         new Employee(){Id=2,Name="Tom",Age=26},
    }
    绑定控件名为listBoxEmplyee的ListBox:
    this.listBoxEmplyee.DisplayMemberPath="Name";//(显示绑定对象的哪个属性)
    this.listBoxEmplyee.SelectedValuePath="Id";//(SelectedValuePath属性将与其SeletedValue属性配合。当调用SeletedValue,ListBox先找到选中的Item所对应的数据对象,然后根据SelectedValuePath的值当作数据对象的属性名称并把这个属性取出来,类似键值对)
    this.listBoxEmplyee.ItemsSource=empList;
    
    

    DispalyMemberPath只能显示简单的字符串,更复杂的形式显示数据需要使用DataTemplate,SelectedValuePath只能返回单一值,如果想进行一些复杂的操作的,使用ListBox的SelectedItem和SelectedItems属性。

    无论把什么数据集合给ListBox,它都会自动包装成ListBoxItem,ListBoxItem就是ListBox对应的ItemContainer。ComboBox对应的是ComboBoxItem,ListBox对应的时ListBoxItem,Menu对应的时MenuItem.....

    3.3 HeaderedItemsControl族

    除了具有ItemsControl的特性外,还能显示标题。

    • 派生至HeaderedItemsControl类
    • 显示列表化的数据,还可以显示标题
    • 内容属性为Items、ItemsSource和Header

    三个控件:MenuItem、TreeViewItem、ToolBar

    3.4 Decorator元素

    窗体装饰效果,比如可以用Border元素给内容加边框,用ViewBox使内容可以自由缩放。

    • 派生自Decorator类
    • UI装饰作用
    • 内容属性为Child
    • 只能由单一元素充当内容
    Decorator族元素
    ButtonChrome ClassicBorderDecorator ListBoxChrome SystemDropsShadowChrome
    Border InkPresenter BulletDecorator ViewBox
    AdornerDecorator      

    3.5 TextBlock和TextBox

             TextBlock只能显示文本,不能编辑,TextBox能编辑。

    3.6 Shape族

    在UI上绘制图形。比如使用Fill属性设置填充效果,使用Stroke属性设置边线效果

    • 派生自Shape类
    • 2D图形绘制
    • 无内容属性

    3.7 Panel族

    所有UI布局的元素都属于这一族。

    • 派生自Panel抽象类
    • 控制UI布局
    • 内容属性为Children
    • 内容可以为多个元素,Panel元素将控制它们布局

    ItemsControl和Panel虽然内容都可以是多个元素,而前者强调的以列表形式展现数据,后者强调的是对包含的元素进行布局。

    Panel族元素
    Canval DockPanel Grid TabPanel
    ToolBarOverflowPanel StackPanel ToolBarPanel UniformGrid
    VirtualizingPanel VistualizingStackPanel WrapPanel  

    4 布局(Layout)

    WPF设计师工作量最大的两部分就是布局与动画,除了点缀性的动画外,大部分动画是布局之间转换。选择合适的布局元素,将会极大地简化编程。

    4.1 布局元素

    布局元素属于Panel族,内容属性为Children ,即可以接受多个控件作为自己的内容并对这些控件进行布局控制。WPF布局理念就是把一个布局元素作为ContentControl或HeaderedContentControl族控件的Contont,再在布局元素里添加要被布局的子集控件

    WPF中的布局元素:

    • Grid:网格,可以自定义行和列并通过行列的数量、行高的列宽来调整控件的布局
    • StackPanel:栈式面板,可以将包含的元素在竖直或水平方向上排成一条线,当移除一个元素,后面的元素会自动向前移动以填充空缺。
    • Canvas:画布,内部元素可以使用以像素为单位的绝对坐标进行定位
    • DockPanel:泊靠式面板,内部元素可以选择泊靠方向
    • WrapPanel:自动折行面板,内部元素在拍满一行后自动折行

    4.2 Grid

    以网格的形式对内容元素们进行布局。

    •  定义任意数量的行和列
    • 行的高度和列的宽度可以使用绝对数值、相对比例或自动调整的方式进行精确设定,并可设置最大和最小值
    • 内部元素可以设置自己所在的行和列,还可以设置自己跨几行,横向跨几列
    • 可以设置Children元素的对齐方向

    Grid适用的场景:

    • UI布局的大框架设计
    • 大量元素需要成行或者成列对齐的情况
    • UI整体尺寸改变时,元素需要保持固有的高度和宽度比例
    • UI后期可能有较大变更或扩展

     4.2.1  定义Grird的行与列

    Grid类具有ColumnDefinitions和RowDefinitions两个属性。表示Grid定义了多少列和多少行。

    <Grid>
       <Grid.ColumnDefinitions>
           <ColumnDefinition/>
           <ColumnDefinition/>
           <ColumnDefinition/>
           <ColumnDefinition/>
       </Grid.ColumnDefinitions>  
       <Grid.RowDefinitions>
           <RowDefinition/>
           <RowDefinition/>
           <RowDefinition/>
       </Grid.RowDefinitions>
    </Grid>
    
    

    当你把鼠标指针在Grid的边缘上移动时会出现提示线,一旦你单击鼠标则会在此添加一条分隔线、创建出新的行和列。也可以在c#动态添加行和列。

    private void Window_Loaded(object sender,RoutedEventArgs e)
    {
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
       this.gridMain.ColumnDefinitions.Add(new ColumnDefinition());
    
    
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.RowDefinitions.Add(new RowDefinition());
       this.gridMain.ShowGridLines=true;
    }

    4.2.2 设置行高和列宽

     计算机图形设计的标准单位时像素(Pixel),所以Grid的宽度和高度单位是像素。

     对于Grid的行高和列宽,可以设置类值:

    • 绝对值:double数值加单位后缀
    • 比例值:double数值后加一个星号
    • 自动值:字符串Auto

     绝对值:一经设定就不会再改变

     比例值:整体尺寸改变时,保持固有比例

     自动值:行高或列宽的实际值将由行列内控件的高度和宽度决定,控件会把行列 撑到合适的宽度和高度。

     例子:

      <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" MinWidth="120"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="4"/>
                <ColumnDefinition Width="80"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="4"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="4"/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>
            <TextBlock Text="请选择您的部门并留言:"    Grid.Column="0" Grid.Row="0" VerticalAlignment="Center"/>
            <ComboBox Grid.Column="1" Grid.Row="0" Grid.ColumnSpan="4"/>
            <TextBox Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="5" BorderBrush="Black"></TextBox>
            <Button Content="提交" Grid.Column="2" Grid.Row="4"/>
            <Button Content="清除" Grid.Column="4" Grid.Row="4"/>
        </Grid>
    • 行和列都是从0开始计数
    • Grid.Row="行编号",若行编号为0,则可忽略,列同样如此
    • 跨行跨列,用Grid.RowSpan="行数" 和Grid.ColumnSpan="列数"
    • 如果两个元素放在Grid,则后写的元素会覆盖先写的,要前面的显示,可把后面写的visibility设置为Hidden或Collapsed,也可以把上面的元素的Opacity属性设置为0

    可拖拽的分隔栏例子:

     <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBox Grid.ColumnSpan="3" BorderBrush="Black"/>
            <TextBox Grid.Row="1" BorderBrush="Black"/>
            <GridSplitter Grid.Row="1" Grid.Column="1"
                          VerticalAlignment="Stretch"
                          HorizontalAlignment="Center"
                          Width="5"
                          Background="Gray"
                          ShowsPreview="True"
                          ></GridSplitter>
            <TextBox Grid.Row="1" Grid.Column="2" BorderBrush="Black"/>
    </Grid>

    4.3 StackPanel

    StackPanel可以把内部元素再纵向或横向紧凑排列,形成栈式布局。类似垒积木。适用以下场景

    • 同样的元素需要紧凑排列
    • 移除其中的元素能够自动补缺的布局或动画
    StackPanel三个属性
    Orientation Horizontal、Vertical 决定内部元素是横向累积还是纵向累积
    HorizontalAlignment Left、Center、Right、Stretch 决定内部元素水平方向的对齐方式
    VerticalAlignment Top、Center、Buttom、Stretch 决定内部元素竖直方向的对齐方式

    例子:

     <Grid>
            <GroupBox Header="请选择没有错别字的成语" BorderBrush="Black" Margin="5">
                <StackPanel Margin="5">
                    <CheckBox Content="A、迫不及待"/>
                    <CheckBox Content="B、首曲一指"/>
                    <CheckBox Content="C、陈腔滥调"/>
                    <CheckBox Content="D、唉声叹气"/>
                    <CheckBox Content="E、不可礼喻"/>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                        <Button Content="清空" Width="60" Margin="5"/>
                        <Button Content="确定" Width="60" Margin="5"/>
                    </StackPanel>
                </StackPanel>
            </GroupBox>
     </Grid>

    4.4 Canvas

    画布,使用Canvas布局与在Windows Form窗体上布局基本一样的。当控件被放置在Canvas就会被附上Canvas.X和Canvas.Y。

    <Canvas>
       <TextBlock Text="用户名:" Canvas.Left="12" Canvas.Top="12"> 
    </Canvas>

    4.5 DockPanel

    DoclPanel内的元素会被附上DockPanel.Dock这个属性,这个属性的数据类型为Dock枚举。Dock枚举可取Left、Top、Right和Bottom四个值。例子:

     <Grid>
            <DockPanel>
                <TextBox DockPanel.Dock="Top" Height="25" BorderBrush="Black"/>
                <TextBox DockPanel.Dock="Left" Width="150" BorderBrush="Black"/>
                <TextBox BorderBrush="Black"/>
            </DockPanel>
    </Grid>

    4.6 WrapPanel

    wrappanel内部采用的是流式布局。WrapPanel使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPenl会排列尽可能多的控件。

    例子:

    <WrapPanel>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
            <Button Width="50" Height="50" Content="OK"/>
    </WrapPanel>

    5 DataBinding

    5.1 Binding基础

    展示层与逻辑层的沟通就使用Data Binding。更新变化是通过类实现INotifyPropertyChanged接口并在属性的set语句中激发PropertyChanged事件。

    例子:

    MainWindow.xmal

    <StackPanel>
            <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5" />
            <Button Content="Add Age" Margin="5" Click="Button_Click"/>
    </StackPanel>

    Student.cs类

     class Student:INotifyPropertyChanged
        {
            public  event PropertyChangedEventHandler PropertyChanged;
            private string name;
            public  string Name
            {
                get { return name; }
                set
                {
                    name = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
                    }
                }
            }
      }

    MainWindow.xaml.cs

        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            Student stu;
            public MainWindow()
            {
                InitializeComponent();
                //stu = new Student();
                //Binding binding = new Binding();
                //binding.Source = stu;
                //binding.Path = new PropertyPath("Name");
                //BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
                this.textBoxName.SetBinding(TextBox.TextProperty, new Binding("Name") { Source=stu=new Student()});
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                stu.Name += "Name";
            }
        }
    }
    

    5.2 Binding的源与路径

      Binding的源就是数据源头——对象,通过属性公开自己的数据。

    5.2.1 把控件作为Binding源与Binding标记扩展

    例子:

    xaml实现:

    <StackPanel>
            <TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider1}" BorderBrush="Black" Margin="5"/>
            <!--Binding Path=Value也常简写成Binding Value-->
            <Slider x:Name="slider1" Maximum="100" Minimum="0" Margin="5" />
    </StackPanel>

    与之等价的C#代码:

    this.textBox1.setBinding(TextBox.TextProperty,new Binding("Value"){ElementName="slider1"});

    5.2.2 控制Binding的方向及数据更新

    控制数据流向的属性的是Mode,BindingMode可取值为TwoWay、OneWay、OnTime、OnWayToSource和Default(根据实际情况确定,比如可编辑的(TextBox.Text)属性,采用双向模式。只读(TextBlock.Text)则单向)。

    上面的例子显示的Text会跟随silder改变,输入Text失去焦点而silder也会改变。失去焦点改变的原因是Binding的属性-UpdateSourceTrigger,类型是UpdateSourceTrigger枚举,可取值为PropertChanged、LostFocus、Explict和Default。默认Default是LostFocus。改成PropertChange,输入时silder就会改变。Binding还有NotifyOnSourceUpdated和NotifyOnTarget两个Bool类型属性,当更新会触发SourceUpdate事件和TargetUpdate事件,通过监听这两个事件找出哪些数据和控件被更新了。

    5.2.3 Binding的路径(Path)

    Binding的Path属性指定关联对象哪个属性。前面的例子等效于:

    Binding binding=new Binding(){Path=new Property("Value"),Source=this.slider1};
    this.textBox1.SetBinding(TextBox.TextProperty,binding);

    或者Binding构造器:

    Binding binding=new Binding("Value"){Source=this.slider1};
    this.textBox1.SetBinding(TextBox.TextProperty,binding);

    支持多级路径,例子(一个TextBox显示另外一个TextBox长度):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" Text="{Binding Path=Text.Length,ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
     </StackPanel>

    等效C#代码:

    this.textBox2.SetBinding(TextBox.TextPropery,new Binding("TextLength"){Source=this.textBox1,Mode=BindingMode.OnWay});

    例子(索引器作为Path示例):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" Text="{Binding Path=Text.[3],ElementName=textBox1,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
     </StackPanel>
    等效于c#代码:
    this.textBox2.SetBinding(TextBox.TextPropery,new Binding("Text.[3]"){Source=this.textBox1,Mode=BindingMode.OnWay});
    
    <!--Text与[3]可省略-->

     

    例子(使用集合作为Binding源):

    List<string> stringList = new List<string>() { "Tim", "Tom", "Blog" };
    this.textBox1.SetBinding(TextBox.TextProperty, new Binding("/") { Source=stringList});
    this.textBox2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList ,Mode=BindingMode.OneWay});
    this.textBox3.SetBinding(TextBox.TextProperty, new Binding("/[2]") { Source = stringList, Mode = BindingMode.OneWay });

    例子(使用集合的子集合作为Binding源,一路斜线下去):

     

    List<Country> countryList = new List<Country>() {  };
    Country country=new Country();
    country.Name = "中国";
    Province province = new Province();
    province.Name = "贵州";
    City city = new City();
    city.Name = "贵阳";
    List < City > listCity=new List<City>();
    listCity.Add(city);
    province.CityList = listCity;
    List<Province> listProvince = new List<Province>();
    listProvince.Add(province);
    country.ProvinceList = listProvince;
    countryList.Add(country);
    this.textBox1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source = countryList });
    this.textBox2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countryList});
    this.textBox3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countryList});
             

    5.2.4 当path属性等于"."

         Binding源就是数据且不需要Path指明,类似string、int等基本类型,他们实例就是本身数据。无法指定通过它哪个属性来范文这个数据,只需将path的值设置为"."。

        例子:

        

    注意,得引用xmlns:sys="clr-namespace:System;assembly=mscorlib"命名空间。

    <Window x:Class="WpfTest16.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfTest16"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            mc:Ignorable="d"
            Title="MainWindow" Height="150" Width="220">
        <StackPanel >
            <StackPanel.Resources>
                <sys:String x:Key="myString">
                      菩提本无树,明镜亦非台
                      本来无一物,何处惹尘埃
                </sys:String>
                
            </StackPanel.Resources>
            <TextBox x:Name="textBox1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>
        </StackPanel>
    </Window>

    上面的代码可以简写成:

    Text="{Binding .,Source={StaticResource ResourceKey=myString}}"
    或
    Text="{Binding Source={StaticResource ResourceKey=myString}}"
    或c#:
    string myString="菩提本无树....."
    this.textBlock1.SetBinding(TextBlock.TextProperty,new Binding("."){Source=myString});
    

    5.2.5 为Binding指定的几种源数据

    • 普通的CLR类型单个对象。
    • 普通的CLR集合类型。比如数组、List<T>、ObservableCollection<T>等集合类型
    • ADO.NET集合类型对象。比如DataTable和DataView等。
    • 使用XmlDataProvider的XML数据
    • 依赖对象,依赖对象的依赖属性可以作为Binding的Path
    • 容器的DataContext
    • 通过ElementName
    • 通过Binding的RelativeSource属性
    • ObjectDataProvider对象
    • 使用LINQ检索的数据对象

    5.2.6 DataContext-没有Source的Binding

    Binding沿着UI元素树往上走寻找DataContext对象并把它作为Source。之所以向上查找,就是因为依赖属性,当没有为控件的某个依赖属性显示赋值时,控件会把自己容器的属性值“借过来”当作自己的属性值。但实际上属性值沿着UI元素树向下传递了。

    例子:

     public class Student
    {
            public int Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
    }
    <Window x:Class="WpfTest17.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfTest17" //必须引用这个,才能使用Student类
            mc:Ignorable="d"
            Title="MainWindow" Height="135" Width="300">
        <StackPanel Background="LightBlue">
            <StackPanel.DataContext>
                <local:Student Id="6" Age="29" Name="Tim"/>
            </StackPanel.DataContext>
            <Grid>
                <StackPanel>
                    <TextBox Text="{Binding Path=Id}" Margin="5"/>
                    <TextBox Text="{Binding Path=Name}" Margin="5"/>
                    <TextBox Text="{Binding Path=Age}" Margin="5"/>
                </StackPanel>
            </Grid>
        </StackPanel>
    </Window>
    

    例子,没有指定Path和Source情况下:

     <StackPanel Background="LightBlue">
            <StackPanel.DataContext>
                <sys:String>Hello DataContent!</sys:String>
            </StackPanel.DataContext>
            <Grid>
                <StackPanel>
                    <TextBlock Text="{Binding}" Margin="5"/>
                    <TextBlock  Text="{Binding}" Margin="5"/>
                    <TextBlock Text="{Binding}" Margin="5"/>
                </StackPanel>
            </Grid>
    </StackPanel>

    比如最外层Grid有DataContext,内层的Button都没有设置DataContext,所以最外层的DataContext会传递到Button那里去。

    <Grid DataContext>
    <Grid>
    <Grid>
       <Button x:Name="btn" Content="OK" Click="btn_Click"/>
    </Grid>
    </Grid>
    </Grid>
    private void btn_click(object sender,RoutedEventArgs e)
    {
       MessageBox.Show(btn.DataContext.toString());
    }

    适合应用场景:

    • 多个控件Binding同一个对象
    • 作为Source的对象不能直接访问,比如B控件Binding A控件,但A控件是private访问级别

    5.2.7 使用集合对象作为列表控件的ItemsSource

    为每一个ItemsSource设置了ItemSource属性值,ItemsControl会自动更新迭代的数据元素,为每个数据元素准备一个条目容器。并使用Binding在条目容器与数据元素之间立起关联。

    例子:

     <StackPanel x:Name="stackPanel"  Background="LightBlue">
            <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="textBoxId" Margin="5"/>
            <TextBlock Text="Student List;"  FontWeight="Bold" Margin="5" />
            <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/>
    </StackPanel>
               //准备数据源
     List<Student> stuList = new List<Student>()
    {
                    new Student(){Id=0,Name="Tim",Age=29},
                    new Student(){Id=1,Name="Tom",Age=28},
                    new Student(){Id=2,Name="Kyle",Age=27},
                    new Student(){Id=3,Name="Toney",Age=26}
     };
      this.listBoxStudents.ItemsSource = stuList;
      this.listBoxStudents.DisplayMemberPath = "Name";
      Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
      this.textBoxId.SetBinding(TextBox.TextProperty, binding);

    去掉DisplayMemberPath,把xaml改成:

    <StackPanel x:Name="stackPanel"  Background="LightBlue">
            <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="textBoxId" Margin="5"/>
            <TextBlock Text="Student List;"  FontWeight="Bold" Margin="5" />
            <ListBox x:Name="listBoxStudents" Height="110" Margin="5">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Path=Id}" Width="30"/>
                            <TextBlock Text="{Binding Path=Name}" Width="30"/>
                            <TextBlock Text="{Binding Path=Age}" Width="30"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
     </StackPanel>

    注意:使用集合类型作为列表控件的ItemsSource一般考虑ObservableCollection<T>,因为接口实现了INotifyCollectionChanged和INotifyPropertyChange,能把集合的变化通知显示的控件。

    5.2.8 使用ADO.NET对象作为Binding的源

    使用ADO.NET类对数控库操作,尽管在流行的软件并不把DataTable的数据直接显示UI列表控件里面而是先通过LINQ等手段把DataTable里的数据转换成恰当的用户自定义类型集合。

    例子:

    XAML:
    <StackPanel Background="LightBlue">
        <ListBox x:Name="listBoxStudents" Height="130" Margin="5"/>
        <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    C#
    private void Button_Click(object sender,RoutedEventArgs e)
    {
         DataTable dt=this.Load();
         this.listBoxStudents.DisplayMemberPath="Name";
         this.listBoxStudents.ItemsSource=dt.DefaultView;
    }
    

    DefaultView属性是DataView类型的对象,DataView类实现了IEnumerable接口,所以可以赋值给ListBox.ItemsSource属性。注意ListView.View是一个ViewBase的对象,ListView是ListBox的派生类,不能当独立控件使用,而GridView是ViewBase派生类,可以当独立控件。

    <StackPanel Background="LightBlue">
        <ListView x:Name="listViewStudents" Height="130" Margin="5">
           <ListView.View>
              <GridView>
                    <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}" />
                    <GridViewColumn Header="Name" Width="60"DisplayMemberBinding="{Binding Name}" />
                    <GridViewColumn Header="Age" Width="60" DisplayMemberBinding="{Binding Age}" />           
               </GridView>
           </ListView.View>
        </ListView>
        <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    

    C#代码:

    private void Button_Click(object sender,RoutedEventArgs e)
    {
         DataTable dt=this.Load();
         this.listViewStudents.ItemsSource=dt.DefaultView;//不能直接使用dt
         /**
         也可以使用:
         this.listViewStudents.DataContext=dt;
         this.listViewStudents.SetBinding(ListView.ItemsSourceProperty,new Binding());
        **/
    }

    GridView的内容属性是Columns,因为XAML支持对内容属性的简写,所以省略了<GridView.Columns>....</GridView.Columns>,直接在<GridView>内容部分定义了GridViewColumn对象,GridViewColumn对象一个属性DisplayMemberBinding,使用它关联数据,而ListBox则使用DisplayMemberPath。如果想要显示更复杂的标题或数据,将GridViewColumn设置HeaderTemplate和CellTemplate。它们都是DataTemplate。

    5.2.9 使用XML数据作为Binding的源

    .NET Framework提供了两套处理XML数据的类库。

    • 符合DOM标准的类库:XmlDocument、XmlElement、XmlNode、XmlAttribute等等
    • LINQ为基础的类库:包括XDocument、XElement、XNode、XAttribute等等,可以使用LINQ进行查询和操作

    XML的文本是树形结构,方便用于线性集合(Array,List)和树形结构数据,因为大多数据传输都基于SOAP(通过对象序列化为XML文本进行传输)相关协议。XML使用的XPath属性而不是Path指定数据来源

    例子:

    RawData.xml文档:

    <?xml version="1.0" encoding="utf-8" ?>
    <StudentList>    
       <Student Id="1">         
        <Name>Tim</Name>     
       </Student >    
       <Student Id="2">        
        <Name>Tom</Name>     
       </Student >    
       <Student Id="3">         
        <Name>Vina</Name>     
       </Student >     
       <Student Id="4">         
        <Name>Emily</Name>     
       </Student >
    </StudentList>
    <StackPanel Background="LightBlue">
            <ListView x:Name="listViewStudents" Height="130" Margin="5">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding XPath=@Id}" />
                        <GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding XPath=Name}" />
                    </GridView>
                </ListView.View>
            </ListView>
            <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                XmlDocument doc = new XmlDocument();
                doc.Load(@"D:\文本\RawData.xml");            
              
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Document = doc;
                xdp.XPath = @"/StudentList/Student";
                this.listViewStudents.DataContext = xdp;
                this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
    }
    
    ==
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                XmlDataProvider xdp = new XmlDataProvider();
                xdp.Source =new Uri(@"D:\文本\RawData.xml");
                xdp.XPath = @"/StudentList/Student";
                this.listViewStudents.DataContext = xdp;
                this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
    }

    @Id和Name指明了关注的路径,加@符号表示的是XML元素的Attribute,不加@符号的字符串表示自己元素

    例子:使用XML作为数据源显示TreeView控件的若干层目录的文件系统(把XML数据和XmlDataProvider对象写在XAML代码)

    <Window.Resources>
            <XmlDataProvider x:Key="xdp" XPath="FileSystem/Folder">
                <x:XData>
                    <FileSystem xmlns="">
                        <Folder Name="Books">
                            <Folder Name="Programming">
                                <Folder Name="Windows">
                                    <Folder Name="WPF"/>
                                    <Folder Name="MFC"/>
                                    <Folder Name="Delphi"/>
                                </Folder>
                            </Folder>
                            <Folder Name="Tools">
                                    <Folder Name="Development"/>
                                    <Folder Name="Designment"/>
                                    <Folder Name="Players"/>
                            </Folder>
                        </Folder>
                    </FileSystem>
                </x:XData>
            </XmlDataProvider>
        </Window.Resources>
        <Grid>
            <TreeView ItemsSource="{Binding Source={StaticResource xdp}}">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}">
                        <TextBlock Text="{Binding XPath=@Name}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Grid>

    5.2.10 使用LINQ检索结果作为Binding的源

    3.0以上的.NET FrameWork开始支持LINQ。使用LINQ可以很方便操作集合对象、DataTable对象和XML对象。

    例子(存储在List集合,查找Name为'T'开头的):

    <StackPanel Background="LightBlue">
            <ListView x:Name="listViewStudents" Height="143" Margin="5">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding Id}" />
                        <GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{Binding Name}" />
                        <GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}" />
                    </GridView>
                </ListView.View>
            </ListView>
            <Button Content="Load" Height="25" Margin="5,0" Click="Button_Click"/>
    </StackPanel>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                List<Student> stuList = new List<Student>()
                {
                    new Student(){Id=0,Name="Tim",Age=29},
                    new Student(){Id=0,Name="Tom",Age=28},
                    new Student(){Id=0,Name="Kyle",Age=27},
                    new Student(){Id=0,Name="Toney",Age=26},
                    new Student(){Id=0,Name="Vina",Age=25},
                    new Student(){Id=0,Name="Mike",Age=24}
                };
                this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;
    }

    存储在DataTable:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
                DataTable dt=this.GetDataTable();
                this.listViewStudents.ItemsSource = from row in dt.Rows.Cast<DataRow>() where Convert.ToString(row["Name"]).StartsWith("T") select new Student{
                   Id=int.Parse(row["Id"].toString()),
                   Name=row["Name"].toString(),
                   Age=int.Parse(row["Age"].toString())
                };
    }

    存储XML(D:\RawData.xml):

    <!--RawData.xml-->
    <?xml version="1.0" encoding="utf-8"?>
    <StudentList>
      <Class>
       <Student Id="0" Name="Tim" Age="29"/>
       <Student Id="1" Name="Tom" Age="28"/> 
       <Student Id="2" Name="Mike" Age="27"/> 
      </Class>
      <Class>
       <Student Id="3" Name="Tony" Age="26"/>
       <Student Id="4" Name="Vina" Age="25"/> 
       <Student Id="5" Name="Emily" Age="24"/> 
      </Class>
    </StudentList>
    
    <!--C#-->
     XDocument xdoc=XDocument.Load(@"D:\RawData.xml");
     this.listViewStudents.ItemsSource = from element in xdoc.Descendants("Student") where element.Attribute("Name").StartsWith("T") select new Student{
                   Id=int.Parse(element.Attribute("Id").Value),
                   Name=element.Attribute("Name").Value,
                   Age=int.Parse(lement.Attribute("Age").Value)
      };
    
    

    5.2.11 使用ObjectDataProvider作为Binding的源

    有时需要方法的返回值,这时需要使用ObjectDataProvider包装Binding源的对象。ObjectDataProvider是把对象作为数据源提供给Binding,是包装一个以方法暴露数据的对象

    例子(加减乘除):

    <!--xmal-->
    <Grid>
      <Button Content="OK" Click="Button_Click" Width="200" Height="200"></Button>
    </Grid>
    <!--c#-->
    private void Button_Click(object sender, RoutedEventArgs e)
    {
                ObjectDataProvider odp = new ObjectDataProvider();
                odp.ObjectInstance = new Calculator();
                odp.MethodName = "Add";
                odp.MethodParameters.Add("100");
                odp.MethodParameters.Add("200");
                MessageBox.Show(odp.Data.ToString());
    }

    例子:

    <StackPanel Background="LightBlue">
            <TextBox x:Name="textBox1Arg1" Margin="5"/>
            <TextBox x:Name="textBox1Arg2" Margin="5"/>
            <TextBox x:Name="textBox1Result" Margin="5"/>
    </StackPanel>
     public MainWindow()
    {
         InitializeComponent();
         this.SetBinding();
    }
     private void SetBinding()
    {
         ObjectDataProvider odp = new ObjectDataProvider();
         odp.ObjectInstance = new Calculator();
         odp.MethodName = "Add";
         odp.MethodParameters.Add("0");
         odp.MethodParameters.Add("0");
         Binding bindingToArg1 = new Binding("MethodParameters[0]")
         {
              Source = odp,
              BindsDirectlyToSource = true,
              UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged      
         };
         Binding bindingToArg2= new Binding("MethodParameters[1]")
         {
              Source = odp,
              BindsDirectlyToSource = true,
              UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
         };
         Binding bindingToResult = new Binding(".") { Source = odp };
         this.textBox1Arg1.SetBinding(TextBox.TextProperty, bindingToArg1);
         this.textBox1Arg2.SetBinding(TextBox.TextProperty, bindingToArg2);
         this.textBox1Result.SetBinding(TextBox.TextProperty, bindingToResult);                   
    }       

    ObjectDataProvider对象作为Source,但使用"."作为Path,当数据源源本身代表数据就是要"."作path,也可省略。实际上三个TextBox都以ObjectDataProvider对象为数据源,只是前两个TextBox在Binding的数据流向做了限制。理由是:

    • ObjectDataProvider的MethodParameters不是依赖属性,不能作为Binding的目标。
    • 数据驱动的UI理念尽可能使用数据对象作为Binding的Source而把UI元素Binding的Target。

    5.2.12 使用Binding的RelativeSource

    控件关联自己某个数据、关联自己某级容器的数据。

    例子:

    <Grid x:Name="g1" Background="Red" Margin="10">
        <DockPanel x:Name="d1" Background="Orange" Margin="10">
             <Grid x:Name="g2" Background="Yellow" Margin="10">
                <DockPanel x:Name="d2" Background="LawnGreen" Margin="10">
                  <TextBox x:Name="textBox1" FontSize="24" Margin="10"></TextBox>
             </DockPanel>
        </Grid>
    </Grid>
    RelativeSource rs = new RelativeSource(RelativeSourceMode.FindAncestor);
    rs.AncestorLevel = 1;
    rs.AncestorType = typeof(Grid);
    Binding binding = new Binding("Name") { RelativeSource = rs };
    this.textBox1.SetBinding(TextBox.TextProperty, binding);
    
    也可以在XAML代码写:
    Text="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}"
    

    AncestorLevel属性是以Binding目标控件为起点的层级偏移量,比如d2的偏移量是1,g2是2,Ancestor属性告诉Binding寻找哪个类型的对象作为自己的源,不是这个类型的对象会被跳过。从自己的第一层向外找,找到第一个Grid类型的对象后把它当作自己的源。

    TextBox关联自身的Name属性:

      RelativeSource rs = new RelativeSource();
      rs.Mode = RelativeSourceMode.Self;
      Binding binding = new Binding("Name") { RelativeSource = rs };
      this.textBox1.SetBinding(TextBox.TextProperty, binding);

    RelativeSource类的Mode属性类型是枚举-PreviousData,TemplatedParent、Self和FindAncestor。实际上3个静态属性是创建一个RelativeSource实例,把实例的Mode属性设置为相应的值,返回这个实例。之所以准备三个属性是为了在XAML代码里直接获取RelativeSource实例。

    5.3 Binding对数据的转换与校验

    Binding作用是架在Source与Target之间桥梁。数据可以桥梁上流通,Binding也可以数据有效性进行校验(ValidationRules属性)。Binding两端要求不同的数据类型时,还可以为数据设置转换器(Converter属性)。

    5.3.1 数据校验

    Binding的ValidationRules属性类型时Collection<ValidationRule>,可以为每个Binding设置多个数据校验对象(ValidationRule类型对象)。需要实现Validate方法,如果校验通过,就把IsValid属性设为True,反之,IsValid属性为False,并为其ErrorContent属性设置消息内容。

    例子:

    <StackPanel>
            <TextBox x:Name="textBox1" Margin="5"/>
            <Slider x:Name="slider1" Minimum="0" Maximum="100" Margin="5"/>
    </StackPanel>
       Binding binding = new Binding("Value") { Source = this.slider1 };
       binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
       RangeValidationRule rvr = new RangeValidationRule();
       binding.ValidationRules.Add(rvr);
       this.textBox1.SetBinding(TextBox.TextProperty, binding);

    RangeValidationRule

     class RangeValidationRule : ValidationRule
       {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                double d = 0;
                if(double.TryParse(value.ToString(),out d))
                {
                    if(d>=0 && d<=100)
                    {
                        return new ValidationResult(true, null);
                    }
                }
                return new ValidationResult(false, "Validation Failed");
            }
    }

    当输入0到100之间正常显示,超过这个区间会显示红色边框。Binding只是在Target被外部方法更新呢时校验数据,而来自Binding的Source数据更新Target时不进行校验的。要校验Source数据时需要将校验条件的ValidatesOnTargetUpdated属性设为true。

    比如把上述的xaml代码改为:

    <Slider x:Name="slider1" Minimum="-10" Maximum="110" Margin="5"/>

      RangeValidationRule rvr = new RangeValidationRule();下面添加:

    rvr.ValidatesOnTargetUpdated = true;

    也会显示校验校验失败。在创建Binding时,把binding的对象的NotifyOnValidationError属性设为true。这样数据校验失败的时候Binding会发出信号,信号以Binding对象的Target为起点在UI元素树上传播,信号到达一个结点,设有侦听器的结点会被触发处理这个信号。处理后,程序员可以选择信号继续向下传播还是就此终止——路由事件。信号在元素树上传递过程被称为路由

    在创建binding实例下面添加:

    binding.NotifyOnValidationError = true;
    this.textBox1.SetBinding(TextBox.TextProperty, binding);
    this.textBox1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.ValidationError));
    private void ValidationError(object sender, RoutedEventArgs e)
    {
                if (Validation.GetErrors(this.textBox1).Count > 0)
                {
                    this.textBox1.ToolTip = Validation.GetErrors(this.textBox1)[0].ErrorContent.ToString();
                }
     }

    会出现下面的效果:

     5.3.2 Binding的数据转换

    Binding还有一种机制叫数据转换,当Source端的Path关联的数据与Target端目标属性数据类型不一致时,可以添加数据转换器。因为上面的Silder的Double和TextBox的String互相转换比较简单,所以WPF类库就自动做了。假设:

    1. Source数据是char、string、enum类型,映射到UI的CheckBx-IsChecked属性的Bool类型。
    2. TextBox输入时登录Button才出现,string与Visibility枚举类型或Bool类型转换。Binding的Mode将是OneWay
    3. Source数据可能是Male或Female,映射到UI的Image控件URI,也是OneWay。

    我们需要创建一个类实现IValueConverter接口,IValueConverter接口有Convert和ConvertBack方法。当数据Binding的Source->Target。调用Convert。反之用ConvertBack。有三个参数,第一个参数为值,保证参数的重用性,第二参数为确定方法的返回类型,第三个参数把额外信息传入方法。Binding的Mode属性会影响两个方法的调用。如果Mode为TwoWay或Default行为与TwoWay一致则两个方法都有可能被调用,如果为OneWay或Default与OneWay一致只有Convert调用。

    例子(向玩家显示各省水果的状态):

    ShuiGuo.cs

     public class ShuiGuo
     {
            public State State { get; set; }
            public Category Category { get; set; }
            public string Name { get; set; }
        }
        public enum State
        {
            Available,
            Locked,
            Unknown
        }
        public enum Category
        {
            xigua,
            caomei
    }

    StateToNullableBoolConverter.cs

     public class StateToNullableBoolConverter : IValueConverter
    {
            //将State转换为Bool
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                State s = (State)value;
                switch(s)
                {
                    case State.Locked:
                        return false;
                    case State.Available:
                        return true;
                    case State.Unknown:
                    default:
                        return null;
                }
           }
      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                bool? nb = (bool?)value;
                switch (nb)
                {
                    case true:
                        return State.Available;
                    case false:
                        return State.Locked;
                    case null:
                    default:
                        return State.Unknown;
                }
            }
    }

    CategoryToSourceConverter.cs

     public class CategoryToSourceConverter:IValueConverter
    {
            public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
            {
                Category c = (Category)value;
                switch(c)
                {
                    case Category.xigua:
                        return @"\Icons\xigua.png";
                    case Category.caomei:
                        return @"\Icons\caomei.png";
                    default:
                        return null;
                }
            }
            public object ConvertBack(object value,Type targetType,object parameter ,CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    }

    CategoryToSourceConverter.xaml

     <StackPanel Background="LightBlue">
            <ListBox x:Name="listBoxShuiGuo" Height="160" Margin="5">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Width="20" Height="20" Source="{Binding Path=Category,Converter={StaticResource cts}}"/>
                            <TextBlock Text="{Binding Path=Name}" Width="60" Margin="80,0"/>
                            <CheckBox IsThreeState="True" IsChecked="{Binding Path=State,Converter={StaticResource stnb}}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <Button x:Name="buttonLoad" Content="Load" Height="25" Margin="5,0" Click="buttonLoad_Click"/>
            <Button x:Name="buttonSave" Content="Save" Height="25" Margin="5,5" Click="buttonSave_Click"/>
    </StackPanel>

    C#

     private void buttonSave_Click(object sender, RoutedEventArgs e)
     {
                StringBuilder sb = new StringBuilder();
                foreach(ShuiGuo sg in listBoxShuiGuo.Items)
                {
                    sb.AppendLine(string.Format("Category={0},Name={1},State={2}", sg.Category, sg.Name, sg.State));
                }
                File.WriteAllText(@"D:\文本\ShuiGuoList.txt",sb.ToString());
     }
    
    private void buttonLoad_Click(object sender, RoutedEventArgs e)
    {
                List<ShuiGuo> shuiGuos = new List<ShuiGuo>()
                {
                    new ShuiGuo(){Category=Category.xigua,Name="贵州",State=State.Unknown},
                    new ShuiGuo(){Category=Category.xigua,Name="甘肃",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="河南",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="东北",State=State.Unknown},
                    new ShuiGuo(){Category=Category.xigua,Name="湖南",State=State.Unknown},
                    new ShuiGuo(){Category=Category.caomei,Name="浙江",State=State.Unknown},
                };
                this.listBoxShuiGuo.ItemsSource = shuiGuos;
    }

     

     5.3.3 MultiBinding(多路Binding) 

    不止一个数据来源就用MultiBinding,能使用Binding对象的场合都能使用MuliBinding,通过MultiBinding把一组Binding对象聚合起来。处在这个集合的Binding对象可以拥有自己的校验与转换机制。汇集起来的数据决定传往MultiBinding目标的数据。

    例子(要求两个用户名和邮箱内容一致):

    Xmal:

     <StackPanel Background="LightBlue">
            <TextBox x:Name="textBox1" Height="23" Margin="5"/>
            <TextBox x:Name="textBox2" Height="23" Margin="5,0"/>
            <TextBox x:Name="textBox3" Height="23" Margin="5"/>
            <TextBox x:Name="textBox4" Height="23" Margin="5"/>
            <Button x:Name="button1" Content="Submit" Width="80" Margin="5"></Button>
    </StackPanel>

    C#:

     public MainWindow()
    {
                InitializeComponent();
                this.SetMulitiBinding();
    }
    public void SetMulitiBinding()
    {
                Binding b1 = new Binding("Text") { Source = this.textBox1 };
                Binding b2 = new Binding("Text") { Source = this.textBox2 };
                Binding b3= new Binding("Text") { Source = this.textBox3 };
                Binding b4= new Binding("Text") { Source = this.textBox4};
    
                MultiBinding mb = new MultiBinding() { Mode = BindingMode.OneWay };
                mb.Bindings.Add(b1);
                mb.Bindings.Add(b2);
                mb.Bindings.Add(b3);
                mb.Bindings.Add(b4);
                mb.Converter = new LogonMultiBindingConvert();
                this.button1.SetBinding(Button.IsEnabledProperty, mb);
       }

     LogonMultiBindingConvert.cs

     public class LogonMultiBindingConvert : IMultiValueConverter
    {
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                if(!values.Cast<string>().Any(text=>string.IsNullOrEmpty(text))&&values[0].ToString()==values[1].ToString()&&values[2].ToString()==values[3].ToString())
                {
                    return true;
                }
                return false;
            }
    
            public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    }

     6 WPF的属性

     6.1 属性

     程序员仍然把字段标记为private但使用一对非private的方法来包装。在这对方法中,一个以Set为前缀且负责判断数据的有效性并写入数据,另一个以Get为前缀且负责把字段里的数据读取出来。

    class Human
    {
      public int Age;
    }
    
    //..
    
    Human h=new Human();
    h.Age=-100;
    h.Age=1000;
    
    //把类设计成这样
    class Human
    {
         private int age;
         public int SetAge
         {
             get{return this.age;}
             set{
                 if(value>=0 &&value<=100)
                 {
                    this.age=value;
                 }else
                 {
                   throw new OverflowException("Age overflow");
                 }
             }
         }
    }

    这种.NET Framework中的属性又称为CLR属性。CLR属性并不会增加内存的负担。再多实例方法只有一个拷贝。

    6.2 依赖属性

    依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值的属性。拥有依赖属性的对象被称为“依赖对象”。

    • 节省实例对内存的开销
    • 属性值可以通过Binding依赖在其他对象中

    6.2.1 依赖属性对内存的使用方式

    而在WPF允许对象在被创建的时候并不包含用于存储数据的空间,只保留在需要用到数据时能够获得默认值、借用其他对象数据或实时分配空间的能力。在WPF系统中,依赖对象时被DependencyObject类实现。具有GetValue和SetValue方法。WPF所有的UI控件都是依赖对象。绝大多数属性已经依赖化。

    6.2.2 声明和使用依赖属性

    
    private void Button_Click(object sender, RoutedEventArgs e)
    {
           Student stu = new Student();
           stu.SetValue(Student.NameProperty, this.textBox1.Text);
           textBox2.Text = (string)stu.GetValue(Student.NameProperty);      
    }
      <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
            <Button Content="OK" Margin="5" Click="Button_Click"></Button>
    </StackPanel>
    class Student:DependencyObject
    {
            public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
    }

    例子进阶(把textBox1和textBox2关联):

    <StackPanel>
            <TextBox x:Name="textBox1" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
            <Button Content="OK" Margin="5" Click="Button_Click"></Button>
    </StackPanel>
     public partial class MainWindow : Window
    {
            Student stu;
            public MainWindow()
            {
                InitializeComponent();
                stu = new Student();
                stu.SetBinding(Student.NameProperty, new Binding("Text") { Source = textBox1 });
                textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu });
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Student stu = new Student();
                stu.Name = this.textBox1.Text;
                this.textBox2.Text = stu.Name;
            }
    }
    class Student : DependencyObject
    {
            public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student));
            public string Name
            {
                get { return (string)GetValue(NameProperty); }
                set { SetValue(NameProperty,value); }
            }
            public BindingExpressionBase SetBinding(DependencyProperty dp,BindingBase binding)
            {
                return BindingOperations.SetBinding(this, dp, binding);
    }

    WPF有一套机制存取依赖属性的值,第一步在DependencyObject派生类中声明public static修饰的DependecyProperty成员变量。并使用DependecyProperty.Regisiter方法获得DependencyProperty的实例。第二步使用DependencyObject的SetValue和GetValue方法,借助DependencyProperty实例存取值。

    6.3 附加属性

    附加属性是说一个属性本来不属于这个对象,但由于某种需求后来附加上。也就是把对象放入一个特定环境后对象才具有的属性称为附加属性。附加属性的作用就是将属性与数据类型解耦,让数据类型设计更加灵活。

    例子(Human在School环境里有Grade附加属性):

    class School:DependencyObject
    {
            public static int GetGrade(DependencyObject obj)
            {
                return (int)obj.GetValue(GradeProperty);
            }
            public static void SetGrade(DependencyObject obj,int value)
            {
                obj.SetValue(GradeProperty, value);
            }
            public static readonly DependencyProperty GradeProperty = DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new UIPropertyMetadata(0));
    }
     class Human:DependencyObject 
    {
    }
     private void Button_Click(object sender, RoutedEventArgs e)
    {
                Human human = new Human();
                School.SetGrade(human, 6);
                int grade = School.GetGrade(human);
                MessageBox.Show(grade.ToString());
    }

    这一过程与依赖属性保存值过程并无二至——值仍然保存在Human实例的EffectiveValueEntry数组里。

    例子(附加属性的Binding):

    <Canvas>
            <Slider x:Name="sliderX" Canvas.Top="10" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
            <Slider x:Name="sliderY" Canvas.Top="40" Canvas.Left="10" Width="260" Minimum="50" Maximum="200"/>
            <Rectangle x:Name="rect" Fill="Blue" Width="30" Height="30" Canvas.Left="{Binding ElementName=sliderX,Path=Value}"
            Canvas.Top="{Binding ElementName=sliderY,Path=Value}"           
             />
    </Canvas>
     this.rect.SetBinding(Canvas.LeftProperty,new Binding("Value") { Source=sliderX});
     this.rect.SetBinding(Canvas.TopProperty, new Binding("Value") { Source = sliderY });

      7  WPF事件

     WPF有两种树,一种是逻辑树,一种是可视元素树。说明CLR直接事件模型中,事件的拥有者就消息的发送者。

     WinForm的Click事件处理器是直接事件模型。

     只要支持事件委托与影响事件的方法签名保持一致,则一个事件可以由多个事件处理器来响应。直接事件模型是传统.NE开发中对象间相互协同、沟通信息的主要手段。当层级组件过多时,会形成事件链,而路由事件很好地解决了这个问题。

     7.1 路由事件

      路由事件拥有者和事件响应者之间没有显式地订阅关系,事件的拥有者只负责激发事件,事件将由谁响应它并不知道,事件的响应值则安装由事件侦听器。

      

    <Grid x:Name="gridRoot" Background="Lime">
            <Grid x:Name="gridA" Margin="10"  Background="Blue" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
                    <Button x:Name="buttonLeft" Content="Left" Width="40" Height="100" Margin="10"/>
                </Canvas>
                <Canvas x:Name="canvasRight" Grid.Column="0" Background="Yellow" Margin="10">
                    <Button x:Name="buttonRight" Content="Left" Width="40" Height="100" Margin="10"/>
                </Canvas>
            </Grid>    
    </Grid>

       

       当点击buttonLeft时,Button.Click事件就会沿着buttonLeft->canvasLeft->gridA->gridRoot->Window这条路线向上传送。下面为gridRoot安装针对Button.Click事件的侦听器。

      this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));

    路由事件是从内部一层一层传递出来最后到达最外层的gridRoot。查看事件的源头用e.OriginalSource,需要as/is操作符或者强制类型转换把它识别/转换成正确的类型。添加事件处理器也可以在XAML中完成。

    <Grid x:Name="gridRoot" Background="Lime" Button.Click="ButtonClicked">

     

    展开全文
  • sl/wpf/mvc 做表现层 wcf做服务层,wf 做逻辑层 ef做数据层 但是如题,这些东西的全称是什么?代表的什么技术??如果新手学习的话怎样去学习? 应该每一步先学习什么,各位前辈指点迷津~
  • 什么是HTML5?什么是WPF/Silverlight?

    千次阅读 2010-01-14 17:39:00
    WPF指的是.NET Framework3.0技术的一个新曾技术,在Vista应用中得到了支持,当然也就影响面大了!做为WPF/E为Windows Presentation Foundation/Everywhere,其中Everywhere指的是跨平台意思,使得在每个操作系统中...
  • wpf笔记

    千次阅读 2014-01-02 22:51:48
    你可以(在根元素或子元素上)声明额外的XML命名空间,但每一个命名空间下的标识符都 必须有一个唯一的前缀,例如,WPF的XAML文件都会使用第二个命名空间加上前缀x(记作xmlns:x而不仅仅是xmlns): xmlns:x=...
  • wpf中,用手指触屏移动或缩放地图时,地图上的车站点没有跟随着一起移动。请问在以下的代码中该如何编写代码,或者在别的事件中怎么写代码了?非常感谢! #region 触屏事件 //当触碰到Image 图片时,...
  • WPF控件

    2019-06-09 19:40:08
    什么是控件?控件是数据和方法的封装,而且控件有自己的方法和属性。WPF设计UI语言是XAML,它能将用户数据处理完成后展示出来。我们可以把控件想像成一个控件是一个容器,容器里面的内容就是控件的内容。控件的...
  • Wpf资源

    2020-12-06 20:51:55
    静态资源(staticResource)的是在程序载入内存时对资源的一次性使用,之后就不再访问这个资源了。 首先创建好一个wpf应用程序,打开我们的visual studio,创建好一个WPF应用程序。 创建好之后,我们需要定义静态...
  • 整个 WPF 就是一个UI框架,一个 UI 框架最重要的是 交互 和 显示 部分,而书写这个功能将会完全贯穿 WPF 整个框架的功能。本文非入门级博客,本文包含了大量链接博客,阅读本文你将会了解从用户手指触摸屏幕到最终...
  • WPF DataTemplate

    2020-07-30 23:05:58
    这里的程序借鉴的的是刘铁猛老师《深入浅出WPF》 为了帮助我更好的学习,我就写了一些自己的理解,可能不太对,希望有路过的大哥大姐指正一下 需求分析:点击右边的小图片与文字,会在左边出现这个图片和文字,并且...
  • WPF杂谈

    2018-02-11 15:26:44
    WPF的最大特色就是数据绑定,它把对象实例的某个属性暴露出来,再绑定给UI元素的暴露属性,从而实现多渠道方向的动态更新(多渠道方向是,可自主选择UI影响Model数据,或Model数据影响UI,或双向互相影响等等)。...
  • WPF基础

    2020-08-28 09:00:02
    WPF基础 前段时间,学了WPF,发现挺有趣的,今天,我们就来讲下WPF吧。WPF是微软推出的基于Windows Vista的用户界面框架,属于.NET Framework 3.0的一部分。它提供了统一的编程模型、语言和框架,真正做到了分离...
  • WPF 概述

    2019-10-02 05:24:15
    WPF 全称是:Windows Presentation Foundation,直译为Windows表示基础。WPF是专门为GUI(Graphic User Interface)程序开发设计的。 在过去很多年,用于GUI开发的技术一直都在改进。不管是Windows Form,还是...
  • WPF命令

    2018-11-13 13:37:00
    命令基本元素及关系WPF里已经有了路由事件,那为什么还需要命令呢?因为事件负责发送消息,对消息如何处理则不管,而命令是有约束力,每个接收者对命令执行统一的行为,比如菜单上的保存,工具栏上的保存都必须是...
  • WPF开发教程

    万次阅读 多人点赞 2019-07-02 23:13:20
    ------WPF开发教程 目录 WPF基础入门.... 3 1. WPF基础之体系结构... 3 2. WPF基础之XAML. 9 3. WPF基础之基元素... 23 4. WPF基础之属性系统... 26 5. WPF基础之路由事件... 33 6. WPF基础之布局系统... ...
  • WPF动画

    2019-07-16 16:03:56
    WPF中Animation类动画分两种:一种线性插值动画、一种关键帧动画。WPF对动画的使用有三种:线性插值、关键帧以及路径。追溯根源其实WPF的动画知识在间隔一段时间内对...线性插值动画的是一种在开始值和结束值之...
  • WPF样式

    2021-07-15 10:21:14
    Width:宽度 Height:高度 FontSize:字体大小 Foreground:前景色 可用于设置文本颜色 ...HorizontalContentAlignment/VerticalContentAlignment,子元素在自身元素中的相对位置。 Vertical..
  • 只需不到 150 行代码就能实现一个支持多顺滑的笔迹书写的应用。当然,这个应用除了笔迹书写外,没有其他任何功能。本文将不会使用 InkCanvas 而是使用更底的方法,通过 Stroke 进行绘制
  • WPF Resource

    2019-07-31 09:54:20
    什么WPF的资源(Resource)? 资源是保存在可执行文件中的一种不可执行数据。在WPF的资源中,几乎可以包含图像、字符串等所有的任意CLR对象,只要对象有一个默认的构造函数和独立的属性。 也就是说,应用程序中非...
  • wpf样式

    2014-03-13 15:16:41
    本文目录  1.引言  2.怎样使用样式?  3.内联样式 ... 4.... 5.... 6.... 7....WPF中的样式的作用,就像Web中的CSS一样,为界面上的元素定制外观,以提供更好的用户界面。在WPF应用程序中,通过控件的
  • WPF 模板

    2019-10-07 01:21:57
    WPF系统不但支持传统的Winfrom编程的用户界面和用户体验设计,更支持使用专门的设计工具Blend进行专业设计,同时还推出了以模板为核心的新一代设计理念。 1.模板的内涵 作为表现形式,每个控件都是为了实现某种...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,167
精华内容 3,666
关键字:

wpf是指什么