我想创建一个简单的按钮模板,其中包含图像和文本。但我想保留系统按钮的外观和感觉。
如何一步步创建它?
P.S.:我已经在 WPF 中使用
CustomControl
和 BasedOn
属性进行了尝试。
您可以使用样式和附加属性轻松完成此操作:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ap="clr-namespace:MyProject.Namespace.Path.To.ButtonProperties">
...
<Style x:Key="ImageButton" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(ap:ButtonProperties.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></Image>
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></ContentPresenter>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
...
</ResourceDictionary>
和
public class ButtonProperties
{
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ButtonProperties), new UIPropertyMetadata((ImageSource)null));
}
然后在标记中:
<Button Style="{StaticResource ImageButton}" ap:ButtonProperties.Image="{StaticResource MyImage}" Content="Test">
</Button>
这个例子看起来相当丑陋,但你可以轻松地将
StackPanel
更改为 Grid
或类似的东西来限制图像比例。使用 ContentPresenter
可以保留按钮的行为,允许您将任何 UIElement
放入其中,并保留命令支持等。
我终于创建了一个带有图像+文本的按钮:
以下是完整代码:
第 1 步:创建一个名为:ImageButtonUC 的新用户控件
<UserControl Name="ImageButton" x:Class="WpfApp.ImageButtonUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button VerticalAlignment="Top" Width="100" Height="25" Click="button_Click">
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="5,0,5,0" Source="{Binding ElementName=ImageButton, Path=Image}"/>
<TextBlock Text="{Binding ElementName=ImageButton, Path=Text}"/>
</StackPanel>
</Button.Content>
</Button>
</Grid>
</UserControl>
第2步:编辑ImageButtonUC.xaml.cs
public partial class ImageButtonUC : UserControl
{
public event RoutedEventHandler Click;
public ImageButtonUC()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButtonUC), new UIPropertyMetadata(""));
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButtonUC), new UIPropertyMetadata(null));
private void button_Click(object sender, RoutedEventArgs e)
{
if (null != Click)
Click(sender, e);
}
}
第3步:在你的xaml中你可以这样使用它: 添加命名空间为
xmlns:Local="clr-namespace:WpfApp"
并将其用作:
<Local:ImageButtonUC x:Name="buttonImg" Width="100" Margin="10,0,10,0" Image="/WpfApp;component/Resources/Img.bmp" Text="Browse..." Click="buttonImg_Click"/>
注意:我的图像位于此处的资源文件夹中
参考:
如果您不想编写任何后台代码,还有另一种方法可以做到这一点(受到 jeffora 答案的启发)。您可以使用控件的内容字段将 URI 放入您想要在按钮中看到的图像:
<Button Content="https://www.google.com/images/srpr/logo3w.png" Height="100" Width="200" Style="{DynamicResource ButtonStyle1}"/>
然后您可以编辑默认按钮样式,如下所示:
<Style>
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true">
<Image x:Name="theImage" Source="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" Margin="4,0,0,0">
<Image.ToolTip>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Image.ToolTip>
</Image>
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
...
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
神奇之处在于“Source=(Binding ...}”部分。对于我来说,在其中使用工具提示来调试丢失/更改的图像效果很好——但也可以轻松删除。
这是我的解决方案!
<Button Content="Browse" Margin="10" Name="btBrowse">
<Button.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical" Height="50" Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image Source="MyIcons\browse.png" Height="30" />
<TextBlock Text="{Binding ElementName=btBrowse, Path=Content}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
结果如下:
另一个答案 - 改进 u/dogracer 和 u/Dave NP:
<Button Content = "{Binding object}" >
<Button.Style >
<Style TargetType="Button">
<Setter Property = "ContentTemplate" >
<Setter.Value >
<DataTemplate >
<StackPanel >
<Image Source="{Binding Path=Content.ImageUrl, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" >
<TextBlock Text = "{Binding Path=Content.Text,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" />
</ StackPanel >
</ DataTemplate >
</ Setter.Value >
</ Setter >
</ Style >
</ Button.Style >
</ Button >
我提供了两种方法的代码,同时保持系统按钮的外观和感觉。
这是输出:
mainWindow.xaml代码
<Window x:Class="ButtonWithImageAndText.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ButtonWithImageAndText"
Title="MainWindow" Height="250" Width="400">
<Window.Resources>
<BitmapImage x:Key="MyImage" UriSource="/panic-button.png"/>
<Style x:Key="ImageButton1" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Image Source="{Binding Path=(local:ButtonProperties.Image), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></Image>
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></ContentPresenter>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ImageButton2" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<CheckBox/>
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></ContentPresenter>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<!--Method-1: Adding Image control through style using DataTemplate and Button.Image AttachedProperty to provide ImageSource. -->
<Button HorizontalContentAlignment="Center" local:ButtonProperties.Image="{StaticResource MyImage}" Content="Test1" Height="20" Style="{StaticResource ImageButton1}"/>
<!--Method-2A: Adding Image control through Button.AddImage AttachedProperty -->
<Button HorizontalContentAlignment="Center" local:ButtonExtensions.AddImage="{StaticResource MyImage}" Content="Test2" Height="20"/>
<!--Method-2B: Adding Image control through Button.AddImage AttachedProperty, also giving flexibility to modify ContentTemplate through style -->
<Button HorizontalContentAlignment="Center" local:ButtonExtensions.AddImage="{StaticResource MyImage}" Content="Test3" Height="20" Style="{StaticResource ImageButton2}"/>
</StackPanel>
</Grid>
</Window>
ButtonExtensions.cs 代码
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ButtonWithImageAndText
{
public static class ButtonExtensions
{
public static ImageSource GetAddImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetAddImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
//Image is added dynamically to control template using OnImageChanged event handler which is raised whenever the AddImage property is assigned some value.
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("AddImage", typeof(ImageSource), typeof(ButtonExtensions), new UIPropertyMetadata((ImageSource)null, OnAddImageChanged));
private static void OnAddImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Button btn)
{
btn.Loaded += (sender, args) =>
{
// ControlTemplate of Button has Border > ContentPresenter. We will change it to Border > StackPanel(Image+ContentPresenter)
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(btn);
var currentParent = VisualTreeHelper.GetParent(contentPresenter) as Border;
if (currentParent != null)
{
StackPanel newStackPanel = new StackPanel()
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = btn.HorizontalContentAlignment
};
UIElement existingChild = currentParent.Child;
currentParent.Child= newStackPanel;
newStackPanel.Children.Add(new Image() { Source = e.NewValue as ImageSource });
// Add the ContentPresenter to the new StackPanel
newStackPanel.Children.Add(existingChild);
}
};
}
}
// Helper method to find a child of a specific type in the visual tree
private static T FindVisualChild<T>(DependencyObject visual)
where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(visual, i);
if (child != null && child is T)
{
return (T)child;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
return null;
}
}
}
ButtonProperties.cs 代码
using System.Windows;
using System.Windows.Media;
namespace ButtonWithImageAndText;
public class ButtonProperties
{
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
//It is assumed that the Image is added to control template using style.
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ButtonProperties), new UIPropertyMetadata((ImageSource)null));
}