WPF Custom/User 带有文本和图标的按钮控件。我应该选择哪一个?
WPF Custom/User Control for button with text and icon. Which one should I choose?
我想使用按钮的自定义控件重构以下代码。
Xaml 文件:LoginWindow.xaml
<Button Grid.Column="0" Style="{DynamicResource BlackLoginButton}" Click="btnLogin_Click" IsDefault="True">
<DockPanel>
<TextBlock Text="text1" VerticalAlignment="Center"/>
<Image Source="../../../Assets/Images/apply.png" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
后面的代码:LoginWindow.xaml.cs
private void btnLogin_Click(object sender, EventArgs e)
{
Login();
}
我特别想要这样的结构:
<local:CustomButton>
Grid.Column="0"
IsDefault="True"
Style="{DynamicResource BlackLoginButton}"
Click="btnLogin_Click"
Text="ciao"
Image="../../../Assets/Images/apply.png">
</local:CustomButton>
我尝试同时使用自定义控件和用户控件。
这是我的UserControl
xaml
<UserControl x:Class="foo.View.CustomUserControl.IconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Style="{DynamicResource BlackLoginButton}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
</UserControl>
后面的代码:
using System.Windows;
using System.Windows.Media;
namespace Mhira3D.View.CustomUserControl
{
public partial class IconButton
{
public static DependencyProperty ClickProperty = DependencyProperty.Register("Click", typeof(RoutedEventHandler),
typeof(IconButton));
public static DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource),
typeof(IconButton));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(IconButton));
public IconButton()
{
InitializeComponent();
}
public RoutedEventHandler Click
{
get { return (RoutedEventHandler) GetValue(ClickProperty); }
set { SetValue(ClickProperty, value); }
}
public ImageSource Image
{
get { return (ImageSource) GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public string Text
{
get { return this.GetValue(TextProperty) as string; }
set { this.SetValue(TextProperty, value); }
}
}
}
这个结构的主要问题是我不能使用Button
属性(我没有继承按钮),比如我不能使用IsDefault
.
我认为可以使用 CustomControl
来更好地使用按钮 属性,就像这样(在这个例子中我只放了 Image
属性):
public class IconButtonCustom : Button
{
static IconButtonCustom()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButtonCustom), new FrameworkPropertyMetadata(typeof(IconButtonCustom)));
}
public ImageSource Image
{
get { return GetValue(SourceProperty) as ImageSource; }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(IconButtonCustom));
}
以及 Generic.xaml
中的样式
<Style TargetType="{x:Type customUserControl:IconButtonCustom}" BasedOn="{StaticResource BlackLoginButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customUserControl:IconButtonCustom}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
现在我有一些问题:
- 以下哪种方法更适合创建我的自定义按钮?
- 使用这两种方法,我在映射点击函数时遇到问题,实际上我得到了
System.Windows.Markup.XamlParseException
,因为 WPF 无法在函数 Click
.[=56 中转换字符串 btnLogin_Click
=]
- 我从未使用过
Generic.xaml
,我的示例是否正确或应该在某些方面进行改进?
好的,所以大多数框架元素都有一个方便的花花公子 属性 称为 Tag,它存在于像这样的实例中,我们可以使用一种方法将某些东西搭载到模板中,而不必去声明额外的依赖属性等等。
因此,如果我们采用默认的 Button
样式模板(右键单击按钮 -> 编辑模板 -> 编辑副本),我们会在其中看到一个 ContentPresenter
,它通常会传递任何 CLR
对象。所以我们很乐意为您提供文本或您想要的任何其他内容。
我们现在有多种选择来实现您的目标。一种是简单地以这种方式(伪)传递你的两个元素;
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock/>
<Image/>
</StackPanel>
</Button>
只是看起来有点乏味。所以我们进入 Style
模板选项,而不是对它做这样的事情;
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type Button}">
<!-- We set a default icon/image path for the instance one isn't defined. -->
<Setter Property="Tag" Value="../../../Assets/Images/apply.png"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- This is where we've added an Image to our Button with it's source bound to Tag. PS - In everything else like Silverlight, WP, UWP, Embedded etc, it's just {TemplateBinding Tag} -->
<Image Grid.Column="1" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
注意添加的 Image
元素,其 Source
设置为 {TemplateBinding Tag}
以及顶部的 setter 指定可能是默认图像的字符串路径.
所以现在我们可以这样做;
<Button Content="Blah Blah Blah" Click="Click_Handler"
Style="{StaticResource SpecialButtonStyle}"/>
...我们会得到一个带有默认图标和您的点击处理程序的按钮。除了现在我们需要为每个实例显示不同的图标。所以我们可以做类似的事情;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="../../../Assets/Images/DIFFERENTIMAGE.png"
Style="{StaticResource SpecialButtonStyle}"/>
除了这似乎仍然有点令人生畏。所以我们可以更进一步,将您的资产字符串变成资源。将 mscorlib 命名空间添加到您的字典中,作为类似 `xmlns:sys" 的前缀,您可以这样做;
<sys:String x:Key="imageONE">../../../Assets/Images/imageONE.png</sys:String>
<sys:String x:Key="imageTWO">../../../Assets/Images/imageTWO.png</sys:String>
<sys:String x:Key="imageTHREE">../../../Assets/Images/imageTHREE.png</sys:String>
这对我们有一些好处。第一,如果您需要更改其中一个图标的文件 path/name,维护会更容易。你可以在一个地方完成它,它会继承到所有实例。而不是必须追踪它很难设置的每个实例。它还允许我们在实例中调用这些作为资源,所以现在我们的 Button
实例看起来像这样;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="{StaticResource imageTWO}"
Style="{StaticResource SpecialButtonStyle}"/>
瞧,大功告成。然而,您应该考虑的另一个观察结果是您的文件路径。我肯定会改用 Pack Uri。只要你的路径是正确的并且你对图像的构建操作也是你应该设置的。
希望这对您有所帮助,干杯。
我想使用按钮的自定义控件重构以下代码。
Xaml 文件:LoginWindow.xaml
<Button Grid.Column="0" Style="{DynamicResource BlackLoginButton}" Click="btnLogin_Click" IsDefault="True">
<DockPanel>
<TextBlock Text="text1" VerticalAlignment="Center"/>
<Image Source="../../../Assets/Images/apply.png" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
后面的代码:LoginWindow.xaml.cs
private void btnLogin_Click(object sender, EventArgs e)
{
Login();
}
我特别想要这样的结构:
<local:CustomButton>
Grid.Column="0"
IsDefault="True"
Style="{DynamicResource BlackLoginButton}"
Click="btnLogin_Click"
Text="ciao"
Image="../../../Assets/Images/apply.png">
</local:CustomButton>
我尝试同时使用自定义控件和用户控件。
这是我的UserControl
xaml
<UserControl x:Class="foo.View.CustomUserControl.IconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Style="{DynamicResource BlackLoginButton}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Button>
</UserControl>
后面的代码:
using System.Windows;
using System.Windows.Media;
namespace Mhira3D.View.CustomUserControl
{
public partial class IconButton
{
public static DependencyProperty ClickProperty = DependencyProperty.Register("Click", typeof(RoutedEventHandler),
typeof(IconButton));
public static DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource),
typeof(IconButton));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(IconButton));
public IconButton()
{
InitializeComponent();
}
public RoutedEventHandler Click
{
get { return (RoutedEventHandler) GetValue(ClickProperty); }
set { SetValue(ClickProperty, value); }
}
public ImageSource Image
{
get { return (ImageSource) GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public string Text
{
get { return this.GetValue(TextProperty) as string; }
set { this.SetValue(TextProperty, value); }
}
}
}
这个结构的主要问题是我不能使用Button
属性(我没有继承按钮),比如我不能使用IsDefault
.
我认为可以使用 CustomControl
来更好地使用按钮 属性,就像这样(在这个例子中我只放了 Image
属性):
public class IconButtonCustom : Button
{
static IconButtonCustom()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconButtonCustom), new FrameworkPropertyMetadata(typeof(IconButtonCustom)));
}
public ImageSource Image
{
get { return GetValue(SourceProperty) as ImageSource; }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(IconButtonCustom));
}
以及 Generic.xaml
<Style TargetType="{x:Type customUserControl:IconButtonCustom}" BasedOn="{StaticResource BlackLoginButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customUserControl:IconButtonCustom}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<TextBlock VerticalAlignment="Center" Text="{Binding ElementName=Button, Path=Text}" />
<Image Source="{Binding ElementName=Button, Path=Image}" Style="{StaticResource ImageButton}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
现在我有一些问题:
- 以下哪种方法更适合创建我的自定义按钮?
- 使用这两种方法,我在映射点击函数时遇到问题,实际上我得到了
System.Windows.Markup.XamlParseException
,因为 WPF 无法在函数Click
.[=56 中转换字符串btnLogin_Click
=] - 我从未使用过
Generic.xaml
,我的示例是否正确或应该在某些方面进行改进?
好的,所以大多数框架元素都有一个方便的花花公子 属性 称为 Tag,它存在于像这样的实例中,我们可以使用一种方法将某些东西搭载到模板中,而不必去声明额外的依赖属性等等。
因此,如果我们采用默认的 Button
样式模板(右键单击按钮 -> 编辑模板 -> 编辑副本),我们会在其中看到一个 ContentPresenter
,它通常会传递任何 CLR
对象。所以我们很乐意为您提供文本或您想要的任何其他内容。
我们现在有多种选择来实现您的目标。一种是简单地以这种方式(伪)传递你的两个元素;
<Button>
<StackPanel Orientation="Horizontal">
<TextBlock/>
<Image/>
</StackPanel>
</Button>
只是看起来有点乏味。所以我们进入 Style
模板选项,而不是对它做这样的事情;
<Style x:Key="SpecialButtonStyle" TargetType="{x:Type Button}">
<!-- We set a default icon/image path for the instance one isn't defined. -->
<Setter Property="Tag" Value="../../../Assets/Images/apply.png"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- This is where we've added an Image to our Button with it's source bound to Tag. PS - In everything else like Silverlight, WP, UWP, Embedded etc, it's just {TemplateBinding Tag} -->
<Image Grid.Column="1" Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
注意添加的 Image
元素,其 Source
设置为 {TemplateBinding Tag}
以及顶部的 setter 指定可能是默认图像的字符串路径.
所以现在我们可以这样做;
<Button Content="Blah Blah Blah" Click="Click_Handler"
Style="{StaticResource SpecialButtonStyle}"/>
...我们会得到一个带有默认图标和您的点击处理程序的按钮。除了现在我们需要为每个实例显示不同的图标。所以我们可以做类似的事情;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="../../../Assets/Images/DIFFERENTIMAGE.png"
Style="{StaticResource SpecialButtonStyle}"/>
除了这似乎仍然有点令人生畏。所以我们可以更进一步,将您的资产字符串变成资源。将 mscorlib 命名空间添加到您的字典中,作为类似 `xmlns:sys" 的前缀,您可以这样做;
<sys:String x:Key="imageONE">../../../Assets/Images/imageONE.png</sys:String>
<sys:String x:Key="imageTWO">../../../Assets/Images/imageTWO.png</sys:String>
<sys:String x:Key="imageTHREE">../../../Assets/Images/imageTHREE.png</sys:String>
这对我们有一些好处。第一,如果您需要更改其中一个图标的文件 path/name,维护会更容易。你可以在一个地方完成它,它会继承到所有实例。而不是必须追踪它很难设置的每个实例。它还允许我们在实例中调用这些作为资源,所以现在我们的 Button
实例看起来像这样;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="{StaticResource imageTWO}"
Style="{StaticResource SpecialButtonStyle}"/>
瞧,大功告成。然而,您应该考虑的另一个观察结果是您的文件路径。我肯定会改用 Pack Uri。只要你的路径是正确的并且你对图像的构建操作也是你应该设置的。
希望这对您有所帮助,干杯。