[英]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
属性(我没有从button继承),例如我不能使用IsDefault
。
我认为这可以替代使用CustomControl
,以更好的方式使用button属性,例如(在此示例中,我仅放置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
转换字符串btnLogin_Click
。 Generic.xaml
,我的示例是正确的还是应该以某种方式进行改进? 好的,所以大多数Framework元素都有一个方便使用的属性,称为Tag ,该属性在诸如此类的实例中存在,在这种情况下,我们可以使用某种方式将某些内容附加到模板中,而不必声明其他依赖项属性等。
因此,如果我们采用默认的Button
样式模板(右键单击button-> Edit Template-> Edit Copy),我们将在其中看到一个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>
这给我们带来了一些好处。 如果您需要更改这些图标之一的文件路径/名称,一种更容易维护的方法。 您可以只在一个位置进行操作,它将继承到所有实例。 不必跟踪每个实例,这很困难。 它还允许我们在实例处将它们作为资源调用,因此现在我们的Button
实例将如下所示;
<Button Content="Blah Blah Blah"
Click="Click_Handler"
Tag="{StaticResource imageTWO}"
Style="{StaticResource SpecialButtonStyle}"/>
瞧,您完成了。 但是,您应该考虑的另一个观察结果是文件路径。 我肯定会改用Pack Uri 。 只要您的路径正确并且对图像的构建操作也应该被设置。
希望这会有所帮助,欢呼。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.