原文:WPF换肤之四:界面设计和代码设计分离

说起WPF来,除了总所周知的图形处理核心的变化外,和Winform比起来,还有一个巨大的变革,那就是真正意义上做到了界面设计和代码设计的分离。这样可以让美工和程序分开进行,而不是糅合在一块,这样做的好处当然也是显而易见的:提高了开发效率。

原先的设计方式

在我们之前设计的代码中,每当添加一个新的窗体的时候,我总是会在这个新的窗体的XAML文件中加入如下的代码,以便使样式能够应用上去:

<Window x:Class="WpfApplication1.MsgWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="391" Width="418" WindowStyle="None" AllowsTransparency="True" Background="Transparent" OpacityMask="White" ResizeMode="NoResize" PreviewMouseMove="ResetCursor" WindowStartupLocation="CenterScreen">
<Grid Background="Transparent">
<Border BorderThickness="5" BorderBrush="DarkGreen" CornerRadius="10,10,10,10" MouseMove="DisplayResizeCursor" PreviewMouseDown="Resize" Name="top">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#eee"/>
</LinearGradientBrush>
</Border.Background>
<Grid>
<!--这里放置UIElement.-->
</Grid>
</Border>
</Grid>
</Window>

然后,在后台中,为了使窗体能够在最大化时不遮蔽任务栏,拖拉窗体边缘能够改变窗口大小,点按窗体可以实现拖拉的时候,在后台加入了如下的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interop;
using System.Diagnostics;
using System.Runtime.InteropServices; namespace WpfApplication1
{
/// <summary>
/// Interaction logic for TestWindow.xaml
/// </summary>
public partial class MsgWindow : Window
{
private const int WM_SYSCOMMAND = 0x112;
private HwndSource hs;
IntPtr retInt = IntPtr.Zero; public MsgWindow()
{
InitializeComponent();
this.SourceInitialized += new EventHandler(WSInitialized);
} void WSInitialized(object sender, EventArgs e)
{
hs = PresentationSource.FromVisual(this) as HwndSource;
hs.AddHook(new HwndSourceHook(WndProc));
} public double relativeClip = ; public enum ResizeDirection
{
Left = ,
Right = ,
Top = ,
TopLeft = ,
TopRight = ,
Bottom = ,
BottomLeft = ,
BottomRight = ,
} [DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); private void ResizeWindow(ResizeDirection direction)
{
SendMessage(hs.Handle, WM_SYSCOMMAND, (IntPtr)( + direction), IntPtr.Zero);
} private void ResetCursor(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton != MouseButtonState.Pressed)
{
this.Cursor = Cursors.Arrow;
}
} private void Resize(object sender, MouseButtonEventArgs e)
{
Border clickedBorder = sender as Border; Point pos = Mouse.GetPosition(this);
double x = pos.X;
double y = pos.Y;
double w = this.ActualWidth;
double h = this.ActualHeight; if (x <= relativeClip & y <= relativeClip) // left top
{
this.Cursor = Cursors.SizeNWSE;
ResizeWindow(ResizeDirection.TopLeft);
}
if (x >= w - relativeClip & y <= relativeClip) //right top
{
this.Cursor = Cursors.SizeNESW;
ResizeWindow(ResizeDirection.TopRight);
} if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
{
this.Cursor = Cursors.SizeNWSE;
ResizeWindow(ResizeDirection.BottomRight);
} if (x <= relativeClip & y >= h - relativeClip) // bottom left
{
this.Cursor = Cursors.SizeNESW;
ResizeWindow(ResizeDirection.BottomLeft);
} if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
{
this.Cursor = Cursors.SizeNS;
ResizeWindow(ResizeDirection.Top);
} if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
{
this.Cursor = Cursors.SizeWE;
ResizeWindow(ResizeDirection.Right);
} if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
{
this.Cursor = Cursors.SizeNS;
ResizeWindow(ResizeDirection.Bottom);
} if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
{
this.Cursor = Cursors.SizeWE;
ResizeWindow(ResizeDirection.Left);
}
} private void DisplayResizeCursor(object sender, MouseEventArgs e)
{
Border clickBorder = sender as Border; Point pos = Mouse.GetPosition(this);
double x = pos.X;
double y = pos.Y;
double w= this.ActualWidth;
double h= this.ActualHeight; this.label1.Content = x + "--" + y; if (x <= relativeClip & y <= relativeClip) // left top
{
this.Cursor = Cursors.SizeNWSE;
}
if (x >= w - relativeClip & y <= relativeClip) //right top
{
this.Cursor = Cursors.SizeNESW;
} if (x >= w - relativeClip & y >= h - relativeClip) //bottom right
{
this.Cursor = Cursors.SizeNWSE;
} if (x <= relativeClip & y >= h - relativeClip) // bottom left
{
this.Cursor = Cursors.SizeNESW;
} if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top
{
this.Cursor = Cursors.SizeNS;
} if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right
{
this.Cursor = Cursors.SizeWE;
} if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom
{
this.Cursor = Cursors.SizeNS;
} if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left
{
this.Cursor = Cursors.SizeWE;
}
} private void button1_Click(object sender, RoutedEventArgs e)
{
this.WindowState = (this.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal);
}
#region 这一部分用于最大化时不遮蔽任务栏
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{ MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO)); // Adjust the maximized size and position to fit the work area of the correct monitor
int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (monitor != System.IntPtr.Zero)
{ MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
} Marshal.StructureToPtr(mmi, lParam, true);
} /// <summary>
/// POINT aka POINTAPI
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
/// <summary>
/// x coordinate of point.
/// </summary>
public int x;
/// <summary>
/// y coordinate of point.
/// </summary>
public int y; /// <summary>
/// Construct a point of coordinates (x,y).
/// </summary>
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
} /// <summary>
/// 窗体大小信息
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
/// <summary> Win32 </summary>
[StructLayout(LayoutKind.Sequential, Pack = )]
public struct RECT
{
/// <summary> Win32 </summary>
public int left;
/// <summary> Win32 </summary>
public int top;
/// <summary> Win32 </summary>
public int right;
/// <summary> Win32 </summary>
public int bottom; /// <summary> Win32 </summary>
public static readonly RECT Empty = new RECT(); /// <summary> Win32 </summary>
public int Width
{
get { return Math.Abs(right - left); } // Abs needed for BIDI OS
}
/// <summary> Win32 </summary>
public int Height
{
get { return bottom - top; }
} /// <summary> Win32 </summary>
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
} /// <summary> Win32 </summary>
public RECT(RECT rcSrc)
{
this.left = rcSrc.left;
this.top = rcSrc.top;
this.right = rcSrc.right;
this.bottom = rcSrc.bottom;
} /// <summary> Win32 </summary>
public bool IsEmpty
{
get
{
// BUGBUG : On Bidi OS (hebrew arabic) left > right
return left >= right || top >= bottom;
}
}
/// <summary> Return a user friendly representation of this struct </summary>
public override string ToString()
{
if (this == RECT.Empty) { return "RECT {Empty}"; }
return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
} /// <summary> Determine if 2 RECT are equal (deep compare) </summary>
public override bool Equals(object obj)
{
if (!(obj is Rect)) { return false; }
return (this == (RECT)obj);
} /// <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
public override int GetHashCode()
{
return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
} /// <summary> Determine if 2 RECT are equal (deep compare)</summary>
public static bool operator ==(RECT rect1, RECT rect2)
{
return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
} /// <summary> Determine if 2 RECT are different(deep compare)</summary>
public static bool operator !=(RECT rect1, RECT rect2)
{
return !(rect1 == rect2);
}
} [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
/// <summary>
/// </summary>
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO)); /// <summary>
/// </summary>
public RECT rcMonitor = new RECT(); /// <summary>
/// </summary>
public RECT rcWork = new RECT(); /// <summary>
/// </summary>
public int dwFlags = ;
} [DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi); [DllImport("User32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
#endregion private void MyMacClass_SourceInitialized(object sender, EventArgs e)
{
hs = PresentationSource.FromVisual((Visual)sender) as HwndSource;
hs.AddHook(new HwndSourceHook(WndProc));
} private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x0024:/* WM_GETMINMAXINFO */
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
break;
default: break;
}
return (System.IntPtr);
} }
}

如果按照上面的设计,那么每加入一个新的窗体,都要重复上面的两个步骤的话,加入一个工程中需要加入的新窗体特别多,估计这种操作足以让一个正常人疯掉了。并且假如以后border的颜色要修改,那得修改多少页面啊~~~

改进的设计方式

所以,为了便于设计和维护,实现所谓的UI和代码分析,让我们提出一个假设的方案来:

首先,所有的公共样式放到一个样式文件中,所有新加的窗体都能共享这个公共的样式文件。

其次,所有的公共事件(窗体最大化,最小化,关闭等),都放到一个公共的基类中,所有新加的窗体只要继承该基类,即可继承系统公用的事件操作。

那么,本着这个假设,我们开始来进行。

首先,对于公共样式,我们需要添加一个“资源词典”的页面,用来设计公共样式:

添加完成后,会看到如下XAML:

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

下面我们在这个文件中添加样式:

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

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

                    x:Class="MyOwnerDrawnWindow.Resource_Dictionaries.MyTheme"

                    >

    <!-- Border defining the frame of the Window -->

    <Style x:Key="MywindowBorder" TargetType="Border">

        <Setter Property="CornerRadius" Value="10, 10, 10, 10" />

        <Setter Property="BorderBrush" Value="DarkGreen"></Setter>

        <Setter Property="BorderThickness" Value="5" />

        <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>

        <Setter Property="VerticalAlignment" Value="Stretch"></Setter>

        <Setter Property="Background" Value="#ababab"></Setter>

    </Style>

    <ControlTemplate x:Key="MyWindowTemplate" TargetType="{x:Type Window}">

        <Grid>

            <Border x:Name="MyBorder"  Style="{StaticResource MywindowBorder}" >

                <Border.Background>

                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

                        <GradientStop Color="#eee"/>

                    </LinearGradientBrush>

                </Border.Background>

                <!--这一句很重要,主要用于放置界面元素,和asp.net中的masterpage有点像-->

                 <AdornerDecorator>

                    <ContentPresenter />

                </AdornerDecorator>

            </Border>

        </Grid>

    </ControlTemplate>

    <!-- My Window Style -->

    <Style x:Key="MyWindowStyle" TargetType="Window">

        <Setter Property="Background" Value="Transparent" />

        <Setter Property="WindowStyle" Value="None" />

        <Setter Property="AllowsTransparency" Value="True" />

        <Setter Property="Template" Value="{StaticResource MyWindowTemplate}" />

    </Style>

</ResourceDictionary>

好了,这就是我们的样式文件,接下来我们需要处理这个样式中的Border事件,让其支持鼠标左键拖拉功能。

新建一个类,命名为MyThemeClass.cs,让其继承自Window基类。在MyThemeClass类中,我们主要处理两个内容,一个是支持鼠标左键拖拉以便改变窗体大小,另一个是使窗体不遮蔽任务栏。

具体代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Media;

using System.Windows.Interop;

using System.Runtime.InteropServices;

using System.Windows.Controls;

using System.Windows.Input;

namespace MyOwnerDrawnWindow

{

    public class MyThemeClass:Window

    {

         private const int WM_SYSCOMMAND = 0x112;

        public const int WM_LBUTTONUP = 0x0202;

        private HwndSource hs;

        IntPtr retInt = IntPtr.Zero;

        public double relativeClip = ;

        public MyThemeClass()

        {

            this.Loaded += delegate

            {

                InitializeEvent();

            };

            this.SourceInitialized +=new EventHandler(MyMacClass_SourceInitialized);

        }

        private void MyMacClass_SourceInitialized(object sender, EventArgs e)

        {

            hs = PresentationSource.FromVisual((Visual)sender) as HwndSource;

            hs.AddHook(new HwndSourceHook(WndProc));

        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

        {

            switch (msg)

            {

                case 0x0024:/* WM_GETMINMAXINFO */

                    WmGetMinMaxInfo(hwnd, lParam);

                    handled = true;

                    break;

                default: break;

            }

            return (System.IntPtr);

        }

        #region 这一部分用于最大化时不遮蔽任务栏

        private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)

        {

            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

            // Adjust the maximized size and position to fit the work area of the correct monitor

            int MONITOR_DEFAULTTONEAREST = 0x00000002;

            System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitor != System.IntPtr.Zero)

            {

                MONITORINFO monitorInfo = new MONITORINFO();

                GetMonitorInfo(monitor, monitorInfo);

                RECT rcWorkArea = monitorInfo.rcWork;

                RECT rcMonitorArea = monitorInfo.rcMonitor;

                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);

                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);

                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);

                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);

            }

            Marshal.StructureToPtr(mmi, lParam, true);

        }

        /// <summary>

        /// POINT aka POINTAPI

        /// </summary>

        [StructLayout(LayoutKind.Sequential)]

        public struct POINT

        {

            /// <summary>

            /// x coordinate of point.

            /// </summary>

            public int x;

            /// <summary>

            /// y coordinate of point.

            /// </summary>

            public int y;

            /// <summary>

            /// Construct a point of coordinates (x,y).

            /// </summary>

            public POINT(int x, int y)

            {

                this.x = x;

                this.y = y;

            }

        }

        [StructLayout(LayoutKind.Sequential)]

        public struct MINMAXINFO

        {

            public POINT ptReserved;

            public POINT ptMaxSize;

            public POINT ptMaxPosition;

            public POINT ptMinTrackSize;

            public POINT ptMaxTrackSize;

        };

        [StructLayout(LayoutKind.Sequential, Pack = )]

        public struct RECT

        {

            /// <summary> Win32 </summary>

            public int left;

            /// <summary> Win32 </summary>

            public int top;

            /// <summary> Win32 </summary>

            public int right;

            /// <summary> Win32 </summary>

            public int bottom;

            /// <summary> Win32 </summary>

            public static readonly RECT Empty = new RECT();

            /// <summary> Win32 </summary>

            public int Width

            {

                get { return Math.Abs(right - left); }  // Abs needed for BIDI OS

            }

            /// <summary> Win32 </summary>

            public int Height

            {

                get { return bottom - top; }

            }

            /// <summary> Win32 </summary>

            public RECT(int left, int top, int right, int bottom)

            {

                this.left = left;

                this.top = top;

                this.right = right;

                this.bottom = bottom;

            }

            /// <summary> Win32 </summary>

            public RECT(RECT rcSrc)

            {

                this.left = rcSrc.left;

                this.top = rcSrc.top;

                this.right = rcSrc.right;

                this.bottom = rcSrc.bottom;

            }

        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

        public class MONITORINFO

        {

            /// <summary>

            /// </summary>           

            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));

            /// <summary>

            /// </summary>           

            public RECT rcMonitor = new RECT();

            /// <summary>

            /// </summary>           

            public RECT rcWork = new RECT();

            /// <summary>

            /// </summary>           

            public int dwFlags = ;

        }

        [DllImport("user32")]

        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        [DllImport("User32")]

        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        #endregion

        #region 这一部分是四个边加上四个角

        public enum ResizeDirection

        {

            Left = ,

            Right = ,

            Top = ,

            TopLeft = ,

            TopRight = ,

            Bottom = ,

            BottomLeft = ,

            BottomRight = ,

        }

        #endregion

        #region 用于改变窗体大小

        [DllImport("user32.dll", CharSet = CharSet.Auto)]

        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private void ResizeWindow(ResizeDirection direction)

        {

            SendMessage(hs.Handle, WM_SYSCOMMAND, (IntPtr)( + direction), IntPtr.Zero);

        }

        #endregion

        #region 为元素注册事件

        private void InitializeEvent()

        {

            ControlTemplate baseWindowTemplate = (ControlTemplate)App.Current.Resources["MyWindowTemplate"];

            Border borderClip = (Border)baseWindowTemplate.FindName("MyBorder", this);

            borderClip.MouseMove += delegate

            {

                DisplayResizeCursor(null,null);

            };

            borderClip.PreviewMouseDown += delegate

            {

                Resize(null,null);

            };

            borderClip.MouseLeftButtonDown += delegate

            {

                DragMove();

            };

            this.PreviewMouseMove += delegate

            {

                ResetCursor(null,null);

            };

        }

        #endregion

        #region 重写的DragMove,以便解决利用系统自带的DragMove出现Exception的情况

        public new void DragMove()

        {

            if (this.WindowState == WindowState.Normal)

            {

                SendMessage(hs.Handle, WM_SYSCOMMAND, (IntPtr)0xf012, IntPtr.Zero);

                SendMessage(hs.Handle, WM_LBUTTONUP, IntPtr.Zero, IntPtr.Zero);

            }

        }

        #endregion

        #region 显示拖拉鼠标形状

        private void DisplayResizeCursor(object sender, MouseEventArgs e)

        {

            Point pos = Mouse.GetPosition(this);

            double x = pos.X;

            double y = pos.Y;

            double w = this.ActualWidth;  //注意这个地方使用ActualWidth,才能够实时显示宽度变化

            double h = this.ActualHeight;

            if (x <= relativeClip & y <= relativeClip) // left top

            {

                this.Cursor = Cursors.SizeNWSE;

            }

            if (x >= w - relativeClip & y <= relativeClip) //right top

            {

                this.Cursor = Cursors.SizeNESW;

            }

            if (x >= w - relativeClip & y >= h - relativeClip) //bottom right

            {

                this.Cursor = Cursors.SizeNWSE;

            }

            if (x <= relativeClip & y >= h - relativeClip)  // bottom left

            {

                this.Cursor = Cursors.SizeNESW;

            }

            if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top

            {

                this.Cursor = Cursors.SizeNS;

            }

            if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right

            {

                this.Cursor = Cursors.SizeWE;

            }

            if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom

            {

                this.Cursor = Cursors.SizeNS;

            }

            if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left

            {

                this.Cursor = Cursors.SizeWE;

            }

        }

        #endregion

        #region  还原鼠标形状

        private void ResetCursor(object sender, MouseEventArgs e)

        {

            if (Mouse.LeftButton != MouseButtonState.Pressed)

            {

                this.Cursor = Cursors.Arrow;

            }

        }

        #endregion

        #region 判断区域,改变窗体大小

        private void Resize(object sender, MouseButtonEventArgs e)

        {

            Point pos = Mouse.GetPosition(this);

            double x = pos.X;

            double y = pos.Y;

            double w = this.ActualWidth;

            double h = this.ActualHeight;

            if (x <= relativeClip & y <= relativeClip) // left top

            {

                this.Cursor = Cursors.SizeNWSE;

                ResizeWindow(ResizeDirection.TopLeft);

            }

            if (x >= w - relativeClip & y <= relativeClip) //right top

            {

                this.Cursor = Cursors.SizeNESW;

                ResizeWindow(ResizeDirection.TopRight);

            }

            if (x >= w - relativeClip & y >= h - relativeClip) //bottom right

            {

                this.Cursor = Cursors.SizeNWSE;

                ResizeWindow(ResizeDirection.BottomRight);

            }

            if (x <= relativeClip & y >= h - relativeClip)  // bottom left

            {

                this.Cursor = Cursors.SizeNESW;

                ResizeWindow(ResizeDirection.BottomLeft);

            }

            if ((x >= relativeClip & x <= w - relativeClip) & y <= relativeClip) //top

            {

                this.Cursor = Cursors.SizeNS;

                ResizeWindow(ResizeDirection.Top);

            }

            if (x >= w - relativeClip & (y >= relativeClip & y <= h - relativeClip)) //right

            {

                this.Cursor = Cursors.SizeWE;

                ResizeWindow(ResizeDirection.Right);

            }

            if ((x >= relativeClip & x <= w - relativeClip) & y > h - relativeClip) //bottom

            {

                this.Cursor = Cursors.SizeNS;

                ResizeWindow(ResizeDirection.Bottom);

            }

            if (x <= relativeClip & (y <= h - relativeClip & y >= relativeClip)) //left

            {

                this.Cursor = Cursors.SizeWE;

                ResizeWindow(ResizeDirection.Left);

            }

        }

        #endregion

    }

}

这样,我们的Theme和类就创建好了,那么,在主窗体中,我们该如何应用上去呢?当然,这个不是难事,并且异常简单:

首先,创建一个新的窗体Window1, 它需要继承自MyThemeClass类:

public partial class Window1 : MyThemeClass

然后再XAML中只需要修改两个地方,首先是添加入一个新的引用:

xmlns:src="clr-namespace:MyOwnerDrawnWindow"

添加完这个引用后,把<Window….></Window>修改成<src:MyThemeClass……></ MyThemeClass> 这样做的目的是防止由于继承基类的不同而造成XAML的识别错误。

最后,只需要添加这句Style="{StaticResource MyWindowStyle}"  就行了。 这样就算是添加完成了新的窗体,每一个新添加的窗体都按照这种方式添加即可,是不是简洁了许多? 并且后期维护也简单多了。

<src:MyThemeClass x:Class="MyOwnerDrawnWindow.Window1"

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

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

    xmlns:src="clr-namespace:MyOwnerDrawnWindow"

    Title="Window1" Height="335" Width="706"

    Style="{StaticResource MyWindowStyle}">

    <Grid>

       <!--这里放置你的UIElement-->

    </Grid>

</src:MyThemeClass>

好了,最后一步就是讲Theme文件关联起来,在APP.xaml文件中添加对资源文件的引用即可:

<Application x:Class="MyOwnerDrawnWindow.App"

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

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

    StartupUri="Window1.xaml">

   <Application.Resources>

        <ResourceDictionary>

            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="MyTheme.xaml"></ResourceDictionary>

            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>

    </Application.Resources>

</Application>

这样运行起来以后,达到的预期的效果。

源码下载

源码下载

WPF换肤之四:界面设计和代码设计分离的更多相关文章

  1. WPF换肤之八:创建3D浏览效果

    原文:WPF换肤之八:创建3D浏览效果 上节中,我们展示了WPF中的异步以及界面线程交互的方式,使得应用程序的显示更加的流畅.这节我们主要讲解如何设计一个具有3D浏览效果的天气信息浏览器. 效果显示 ...

  2. WPF换肤之六:酷炫的时区浏览小精灵

    原文:WPF换肤之六:酷炫的时区浏览小精灵 由于工作需要,经常要查看到不同地区的 当前时间,以前总是对照着时区表来进行加减运算,现在有了这个小工具以后,感觉省心了不少.下面是软件的截图: 效果图赏析 ...

  3. WPF换肤之二:可拉动的窗体

    原文:WPF换肤之二:可拉动的窗体 让我们接着上一章: WPF换肤之一:创建圆角窗体 来继续. 在这一章,我主要是实现对圆角窗体的拖动,改变大小功能. 拖动自绘窗体的步骤 首先,通过上节的设计,我们知 ...

  4. WPF换肤之一:创建圆角窗体

    原文:WPF换肤之一:创建圆角窗体 我们都期望自己的软件能够有一套看上去很吸引人眼球的外衣,使得别人看上去既专业又有美感.这个系列就带领着大家一步一步的讲解如何设计出一套自己的WPF的窗体皮肤,如果文 ...

  5. WPF换肤之七:异步

    原文:WPF换肤之七:异步 在WinForm时代,相信大家都遇到过这种情形,如果在程序设计过程中遇到了耗时的操作,不使用异步会导致程序假死.当然,在WPF中,这种情况也是存在的,所以我们就需要寻找一种 ...

  6. WPF换肤之三:WPF中的WndProc

    原文:WPF换肤之三:WPF中的WndProc 在上篇文章中,我有提到过WndProc中可以处理所有经过窗体的事件,但是没有具体的来说怎么可以处理的. 其实,在WPF中,要想利用WndProc来处理所 ...

  7. WPF换肤之五:创建漂亮的窗体

    原文:WPF换肤之五:创建漂亮的窗体 换肤效果 经过了前面四章的讲解,我们终于知道了如何拖拉窗体使之改变大小,也知道了如何处理鼠标事件,同时,也知道了如何利用更好的编写方式来编写一个方便实用和维护的换 ...

  8. 有点激动,WPF换肤搞定了!

    一如既往没废话! wpf桌面应用开发都是window内引入很多个UserControl. 如果你有通过不同颜色来换肤的需求,那么下面我就将整个过程! 分2个步骤: 1.主窗体背景色替换: 2.同时界面 ...

  9. 做个开源博客学习Vite2 + Vue3 (三)博客设计和代码设计

    项目搭建好了之后是不是可以编码了呢? 等等不要着急,我们是不是应该先设计一下?比如博客的功能等? 博客设计 先做个简单的个人博客,因为是个人版,所以可以省略注册.登录这些功能,表结构也可以简单一点. ...

随机推荐

  1. ADOConnection数据库连接池

    unit AdoconnectPool; interface uses Classes, Windows, SysUtils, ADODB, IniFiles, forms; type TADOCon ...

  2. 【ASP.NET Web API教程】1 ASP.NET Web API入门

    原文 [ASP.NET Web API教程]1 ASP.NET Web API入门 Getting Started with ASP.NET Web API第1章 ASP.NET Web API入门 ...

  3. Q10Ⅱ 双核 - 产品中心 - 海美迪

    海美迪Q系列视频文明书 Q10Ⅱ 双核 - 产品中心 - 海美迪

  4. 与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载)

    原文:与众不同 windows phone (13) - Background Task(后台任务)之后台文件传输(上传和下载) [索引页][源码下载] 与众不同 windows phone (13) ...

  5. perl的一些基本用法

    ReadLine support available (try 'install Bundle::CPAN')cpan>进入cpan的shell,好了,我为了安装spamassassin,需要安 ...

  6. 大白菜U盘启动制作工具装机维护版V5.0–大白菜U盘下载中心

    大白菜U盘启动制作工具装机维护版V5.0–大白菜U盘下载中心   大白菜U盘启动制作工具装机维护版V5.0

  7. 降低成本是永恒的追求(xamarin)

    减少为主线的成本始终是一个社会经济发展.经济活动似乎很.商业模式的出现相关.我记得早起写Web程序,真正的企业并不多忙.大部分时间处理与浏览器的问题之间的差异所带来. 有些型号也做了屏蔽这样的差别,有 ...

  8. Android万能适配器base-adapter-helper的源代码分析

    项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter ...

  9. UVA 10892 LCM Cardinality(数论 质因数分解)

    LCM Cardinality Input: Standard Input Output: Standard Output Time Limit: 2 Seconds A pair of number ...

  10. 【VBA研究】查找目录以下全部文件的名称

    作者:iamlaosong 目录里面保存有面单扫描的图像文件,文件名称为邮件号码.如今想收集这些邮件号码,由于量非常大,不可能一个一个的截取,仅仅能通过程序实现.假定,当前工作表B列里放的是存放这些图 ...