WPF-CustomControl(控件库)

目录
起因
偶然间看到了下面这种用法,之前记录过自定义控件的开发:通用开发步骤,只不过不跨程序集,下面的这种是类似控件库的开发,特此记录。
<super:BaseWindow
x:Class="SuperCom.Window_Monitor"
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:super="https://github.com/SuperStudio/SuperControls"
Title="监视器"
Width="1000"
Height="600"
Background="{DynamicResource Window.Background}"
ContentRendered="BaseWindow_ContentRendered"
Foreground="{DynamicResource Window.Foreground}"
Icon="pack://application:,,,/Resources/Ico/ICON.ico"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<!-- 省略具体逻辑 -->
</Grid>
</super:BaseWindow>示例
项目结构:
Solution
├─ DemoApp 主程序
└─ MyControl 自定义控件库在 MyControl 中可以将 CustomControl1.cs, Themes/Generic.xaml 删除
新建:
BaseWindow.csusing System.Windows;
namespace SuperControls
{
public class BaseWindow : Window
{
public BaseWindow()
{
Width = 800;
Height = 500;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
Title = "我是 BaseWindow";
Background = System.Windows.Media.Brushes.White;
}
}
}让 XAML 能识别前缀( XmlnsDefinition定义)
打开:
MyControl/Properties/AssemblyInfo.cs如果没有,就新建。
内容:
using System.Windows.Markup;
[assembly: XmlnsDefinition(
"https://hexin.pw/MyControl",
"MyControl")]意思:
XAML URI
↓
映射到
↓
namespace MyControl这一步就是:
xmlns:super="https://hexin.pw/MyControl"引用DLL
主程序引用控件库
右键:
DemoApp
→ 添加项目引用勾选:
MyControl使用
<mycontrol:BaseWindow
x:Class="DemoApp.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:local="clr-namespace:DemoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mycontrol="https://hexin.pw/MyControl"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="30"
Text="Hello BaseWindow" />
</Grid>
</mycontrol:BaseWindow>using MyControl;
namespace DemoApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : BaseWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}附:升级版示例
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MyControl
{
/// <summary>
/// 自定义无边框窗口基类,支持拖拽、最大化还原、窗口缩放。
/// </summary>
public class BaseWindow : Window
{
#region 依赖属性
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register(
nameof(Header),
typeof(string),
typeof(BaseWindow),
new PropertyMetadata(string.Empty));
/// <summary>标题栏显示文本</summary>
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
#endregion
#region 模板部件名称
private const string PART_TitleBar = "PART_TitleBar";
private const string PART_MinButton = "PART_MinButton";
private const string PART_MaxButton = "PART_MaxButton";
private const string PART_CloseButton = "PART_CloseButton";
private const string PART_MaxIcon = "PART_MaxIcon";
#endregion
#region 字段
private Border _titleBar;
private Button _minButton;
private Button _maxButton;
private Button _closeButton;
private Path _maxIcon;
// 最大化图标路径(双矩形)
private const string RestoreIconData =
"M2.5,0.5 L10.5,0.5 L10.5,8.5 L2.5,8.5 Z M0.5,2.5 L0.5,10.5 L8.5,10.5 L8.5,8.5 L2.5,8.5 L2.5,2.5 Z";
// 正常状态图标路径(单矩形)
private const string NormalIconData =
"M0.5,1.5 L9.5,1.5 L9.5,10.5 L0.5,10.5 Z";
#endregion
static BaseWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(BaseWindow),
new FrameworkPropertyMetadata(typeof(BaseWindow)));
}
// 实例构造函数:所有继承 BaseWindow 的窗口默认居中显示
public BaseWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// 获取标题栏,用代码挂载鼠标事件
_titleBar = GetTemplateChild(PART_TitleBar) as Border;
if (_titleBar != null)
{
_titleBar.MouseLeftButtonDown += TitleBar_MouseLeftButtonDown;
}
_minButton = GetTemplateChild(PART_MinButton) as Button;
_maxButton = GetTemplateChild(PART_MaxButton) as Button;
_closeButton = GetTemplateChild(PART_CloseButton) as Button;
_maxIcon = GetTemplateChild(PART_MaxIcon) as Path;
if (_minButton != null)
{
_minButton.Click += (s, e) => WindowState = WindowState.Minimized;
}
if (_maxButton != null)
{
_maxButton.Click += (s, e) =>
{
WindowState = WindowState == WindowState.Maximized
? WindowState.Normal
: WindowState.Maximized;
};
}
if (_closeButton != null)
{
_closeButton.Click += (s, e) => Close();
}
// 初始化图标
UpdateMaxIcon();
StateChanged += (_, e) => UpdateMaxIcon();
}
private void UpdateMaxIcon()
{
if (_maxIcon == null)
{
return;
}
_maxIcon.Data = Geometry.Parse(WindowState == WindowState.Maximized ? RestoreIconData : NormalIconData);
}
#region 标题栏拖拽 + 双击最大化
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
if (e.ClickCount == 2)
{
WindowState = WindowState == WindowState.Maximized
? WindowState.Normal
: WindowState.Maximized;
return;
}
DragMove();
}
}
#endregion
#region 窗口缩放(WM_NCHITTEST)
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hwndSource = PresentationSource.FromVisual(this) as HwndSource;
hwndSource?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_NCHITTEST = 0x0084;
if (msg == WM_NCHITTEST)
{
short x = (short)(lParam.ToInt64() & 0xFFFF);
short y = (short)((lParam.ToInt64() >> 16) & 0xFFFF);
var pos = PointFromScreen(new Point(x, y));
double resizeBorder = 6;
double actualWidth = ActualWidth;
double actualHeight = ActualHeight;
if (WindowState == WindowState.Maximized)
return IntPtr.Zero;
bool onLeft = pos.X <= resizeBorder;
bool onRight = pos.X >= actualWidth - resizeBorder;
bool onTop = pos.Y <= resizeBorder;
bool onBottom = pos.Y >= actualHeight - resizeBorder;
const int HTLEFT = 10;
const int HTRIGHT = 11;
const int HTTOP = 12;
const int HTBOTTOM = 15;
const int HTTOPLEFT = 13;
const int HTTOPRIGHT = 14;
const int HTBOTTOMLEFT = 16;
const int HTBOTTOMRIGHT = 17;
if (onTop && onLeft) { handled = true; return (IntPtr)HTTOPLEFT; }
if (onTop && onRight) { handled = true; return (IntPtr)HTTOPRIGHT; }
if (onBottom && onLeft) { handled = true; return (IntPtr)HTBOTTOMLEFT; }
if (onBottom && onRight) { handled = true; return (IntPtr)HTBOTTOMRIGHT; }
if (onLeft) { handled = true; return (IntPtr)HTLEFT; }
if (onRight) { handled = true; return (IntPtr)HTRIGHT; }
if (onTop) { handled = true; return (IntPtr)HTTOP; }
if (onBottom) { handled = true; return (IntPtr)HTBOTTOM; }
}
return IntPtr.Zero;
}
#endregion
}
}Themes/Generic.xaml
Generic.xaml
→ 属性
→ Build Action改成:
Page // 一般默认就是 Page<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControl">
<!-- ===== 标题栏按钮通用样式 ===== -->
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#CCCCCC" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Bd"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#3E3E42" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#4F4F56" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ===== 关闭按钮样式(悬停红色) ===== -->
<Style
x:Key="CloseButtonStyle"
BasedOn="{StaticResource TitleBarButtonStyle}"
TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
x:Name="Bd"
Background="Transparent"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#E81123" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#F1707A" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ===== BaseWindow 主样式 ===== -->
<Style TargetType="{x:Type local:BaseWindow}">
<!-- 默认属性 -->
<Setter Property="Background" Value="#1E1E1E" />
<Setter Property="Foreground" Value="#CCCCCC" />
<Setter Property="WindowStyle" Value="None" />
<Setter Property="AllowsTransparency" Value="True" />
<Setter Property="ResizeMode" Value="CanResize" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Header" Value="My Application" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BaseWindow}">
<!-- 外层容器:阴影 + 圆角 -->
<Border
x:Name="PART_OuterBorder"
Margin="6"
Background="{TemplateBinding Background}"
BorderBrush="#3F3F46"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="8"
SnapsToDevicePixels="True">
<Border.Effect>
<DropShadowEffect
BlurRadius="12"
Opacity="0.5"
ShadowDepth="2"
Color="#000000" />
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- ========== 标题栏 ========== -->
<Border
x:Name="PART_TitleBar"
Grid.Row="0"
Background="#2D2D30"
CornerRadius="8,8,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- 应用图标 -->
<Path
Grid.Column="0"
Width="14"
Height="14"
Margin="14,0,0,0"
VerticalAlignment="Center"
Data="M7,0 L14,7 L7,14 L0,7 Z"
Fill="#569CD6"
Stretch="Uniform" />
<!-- 标题文字 -->
<TextBlock
Grid.Column="1"
Margin="8,0,0,0"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="13"
FontWeight="SemiBold"
Foreground="#FFFFFF"
Text="{TemplateBinding Header}"
TextTrimming="CharacterEllipsis" />
<!-- 窗口控制按钮 -->
<StackPanel
Grid.Column="2"
VerticalAlignment="Top"
Orientation="Horizontal">
<!-- 最小化 -->
<Button
x:Name="PART_MinButton"
Width="46"
Height="40"
Style="{StaticResource TitleBarButtonStyle}"
ToolTip="最小化">
<Path
Width="10"
Height="1"
Data="M0,0 L10,0"
Stroke="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeThickness="1" />
</Button>
<!-- 最大化 / 还原 -->
<Button
x:Name="PART_MaxButton"
Width="46"
Height="40"
Style="{StaticResource TitleBarButtonStyle}"
ToolTip="最大化">
<!-- 默认:正常状态图标 -->
<Path
x:Name="PART_MaxIcon"
Width="10"
Height="10"
Data="M0.5,1.5 L9.5,1.5 L9.5,10.5 L0.5,10.5 Z"
Stroke="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeThickness="1" />
</Button>
<!-- 关闭 -->
<Button
x:Name="PART_CloseButton"
Width="46"
Height="40"
Style="{StaticResource CloseButtonStyle}"
ToolTip="关闭">
<Path
Width="10"
Height="10"
Data="M0,0 L10,10 M10,0 L0,10"
Stretch="Uniform"
Stroke="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"
StrokeEndLineCap="Round"
StrokeStartLineCap="Round"
StrokeThickness="1.2" />
</Button>
</StackPanel>
</Grid>
</Border>
<!-- ========== 内容区 ========== -->
<Border Grid.Row="1" CornerRadius="0,0,8,8">
<AdornerDecorator>
<ContentPresenter Margin="0" />
</AdornerDecorator>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- 最大化时去掉阴影和圆角 -->
<Trigger Property="WindowState" Value="Maximized">
<Setter TargetName="PART_OuterBorder" Property="Margin" Value="0" />
<Setter TargetName="PART_OuterBorder" Property="CornerRadius" Value="0" />
<Setter TargetName="PART_TitleBar" Property="CornerRadius" Value="0" />
<Setter TargetName="PART_OuterBorder" Property="Effect" Value="{x:Null}" />
<Setter TargetName="PART_MaxButton" Property="ToolTip" Value="还原" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>注册 Generic.xaml
在App.xaml.cs 或 AssemblyInfo.cs,一般都在AssemblyInfo.cs。
[assembly: ThemeInfo(
ResourceDictionaryLocation.None,
ResourceDictionaryLocation.SourceAssembly
)]确保存在,一般默认有。
主程序使用:
<mycontrol:BaseWindow
x:Class="DemoApp.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:local="clr-namespace:DemoApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mycontrol="https://hexin.pw/MyControl"
Width="1000"
Height="600"
Header="我的超级窗口">
<Grid>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="40"
Text="Hello Super Window" />
</Grid>
</mycontrol:BaseWindow>using MyControl;
namespace DemoApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : BaseWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}