为什么自定义控件 "ImageButton" 不显示它的图像?

Why does a custom control "ImageButton" not display it's image?

我正在基于 MahApps 的 AccentedSquareButtonStyle 编写具有突出显示效果的图像按钮自定义控件。 ImageButton.xaml:

<UserControl x:Class="NQR_GUI_WPF.ImageButton"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:NQR_GUI_WPF"
         mc:Ignorable="d" >
<Button Style="{StaticResource AccentedSquareButtonStyle}" Background="Transparent" Foreground="Transparent" BorderThickness="0" Width="24" Height="24" TouchDown="Button_TouchDown">
    <Grid Background="Transparent">
        <ContentControl>
            <ContentControl.Style>
                <Style TargetType="{x:Type ContentControl}">
                    <Setter Property="Content" Value="{Binding Image, RelativeSource={RelativeSource TemplatedParent}}"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=IsMouseOver}" Value="True" >
                            <Setter Property="Content" Value="{Binding HighlightedImage, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=IsPressed}" Value="True" >
                            <Setter Property="Content" Value="{Binding ClickedImage, RelativeSource={RelativeSource TemplatedParent}}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </Grid>
</Button>

ImageButton.xaml.cs:

namespace NQR_GUI_WPF
{
/// <summary>
/// Interaction logic for ImageButton.xaml
/// </summary>
public partial class ImageButton : UserControl
{
    public static DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(Canvas), typeof(ImageButton));
    public static DependencyProperty ClickedImageProperty = DependencyProperty.Register("ClickedImage", typeof(Canvas), typeof(ImageButton));
    public static DependencyProperty HighlightedImageProperty = DependencyProperty.Register("HighlightedImage", typeof(Canvas), typeof(ImageButton));

    static ImageButton()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
    }

    public Canvas Image
    {
        get { return (Canvas)base.GetValue(ImageProperty); }
        set { base.SetValue(ImageProperty, value); }
    }

    public Canvas ClickedImage
    {
        get { return (Canvas)base.GetValue(ClickedImageProperty); }
        set { base.SetValue(ClickedImageProperty, value); }
    }

    public Canvas HighlightedImage
    {
        get { return (Canvas)base.GetValue(HighlightedImageProperty); }
        set { base.SetValue(HighlightedImageProperty, value); }
    }

    private void Button_TouchDown(object sender, TouchEventArgs e)
    {
        Keyboard.ClearFocus();
    }
}

}

示例图标:

<Canvas x:Key="printIcon" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="appbar_printer_text" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
            <Path Width="44" Height="45" Canvas.Left="16" Canvas.Top="17" Stretch="Fill" Fill="{Binding Source={x:Static prop:Settings.Default}, Path=theme, Converter={StaticResource idealForegroundConverter}}" Data="F1 M 25,27L 25,17L 51,17L 51,27L 47,27L 47,21L 29,21L 29,27L 25,27 Z M 16,28L 60,28L 60,51L 52,51L 52,46L 55,46L 55,33L 21,33L 21,46L 24,46L 24,51L 16,51L 16,28 Z M 25,39L 28,39L 28,52L 35,52L 35,59L 48,59L 48,39L 51,39L 51,62L 33,62L 25,54L 25,39 Z M 46,55L 38,55L 38,52L 46,52L 46,55 Z M 46,49L 30,49L 30,46L 46,46L 46,49 Z M 46,43L 30,43L 30,40L 46,40L 46,43 Z "/>
        </Canvas>

问题是在MainWindow中,添加存储在App.xaml中的图像后,控件是空的(没有显示图像)。

<local:ImageButton Image="{StaticResource printIcon}" HighlightedImage="{StaticResource printIconHighlighted}" ClickedImage="{StaticResource printIconClicked}" Grid.Column="1" HorizontalAlignment="Left" Height="46" Margin="36,10,0,0" VerticalAlignment="Top" Width="100"/>

我试过将图像直接绑定到控件模板中,但没有成功(尽管在控件设计器视图中显示了图像)。 为什么不显示控件图像?

您使用 TemplateParent 不正确

而不是这个

{Binding Image, RelativeSource={RelativeSource TemplatedParent}}

应该是这样的

{Binding RelativeSource={RelativeSource Mode=FindAncestor,
 AncestorType=ImageButton}, Path=Image}

我在下面这样做了,

 <Controls:MetroWindow.Resources>
    <ImageBrush Stretch="Fill"  x:Key="CloseImage" ImageSource="../images/Close.png" />
    <ImageBrush x:Key="CloseImageRed" ImageSource="../images/CloseRed.jpg" />
  </Controls:MetroWindow.Resources>

<Button>
       <Button.Style>
         <Style TargetType="Button">
           <Setter Property="Background" Value="{StaticResource CloseImageRed}"/>
           <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="{x:Type Button}">
                      <Border Background="{TemplateBinding Background}"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="{TemplateBinding BorderThickness}">
               <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                 Margin="{TemplateBinding Padding}" 
                                 VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                 SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                 RecognizesAccessKey="True"/>
                      </Border>
                     <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                           <Setter Property="Background" Value="{StaticResource CloseImage}"/> 
                         </Trigger>
                     </ControlTemplate.Triggers>
               </ControlTemplate>
           </Setter.Value>
       </Setter>                                                    
  </Style>
 </Button.Style>
</Button>

看看。

您正在为您的自定义按钮设置 UserControl.Content,我认为您想要设置的是 UserControl.ContentTemplate

.Content 中,没有要绑定的 "TemplatedParent"。但是,如果这是 Template,则 TemplatedParent 将指向为其定义模板的 UserControl。在这种情况下,它将引用您的 ImageButton UserControl,它可以正确地让您访问 Image 属性。

<UserControl ..>
    <UserControl.ContentTemplate>
        <ControlTemplate>
            <!-- TemplatedParent bindings should refer to UserControl from here -->
            <Button ... /> 
        </ControlTemplate>
    </UserControl.ContentTemplate>
</UserControl>

这也允许你写类似

的东西
<local:ImageButton Content="Some Text" />

没有用包含 "Some Text"

的文本元素完全替换 Button XAML 代码

例如,您现在拥有的内容将呈现为

<UserControl>
    <Button /> <!-- Button is .Content, and can be replaced by XAML using the control -->
</UserControl>

如果它是一个 ContentTemplate,它将呈现为

<UserControl>
    <Button> <!-- Button is ContentTemplate, so wraps any content given by external XAML -->
        <Content />
    </Button>
</UserControl>

A UserControl 不是您的最佳选择。 UserControl 不适用于编写通用 WPF 控件。你可以做到,但这不是最简单的方法。最简单的方法是继承常规控件(通常只是 ContentControlHeaderedContentControl),然后为它编写样式和模板。一旦你掌握了这项技术,你就可以根据需要敲打它们。通常您可以只为现有控件编写专门的模板,但在您的情况下,您确实需要 Button 的自己的子类。

我会将 ImageButton 编写为 Button 的子类,附加的依赖属性与您定义的非常相似,但我会将它们设为 Object 类型因此消费者可以在其中填充 XAML 可以呈现的任何内容。没有理由不给他们所有他们可以使用的绳索。我将使用 Content 属性 而不是 Image 属性,因为这样可以简化事情。

如果出于某种原因您需要防止非图像内容,您可以使用比 Object 更专业的内容类型,但您没有提及引入该限制的任何特定原因。

C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace NQR_GUI_WPF
{
    /// <summary>
    /// Interaction logic for ImageButton.xaml
    /// </summary>
    public class ImageButton : Button
    {
        public ImageButton()
        {
            TouchDown += ImageButton_TouchDown;
        }

        private void ImageButton_TouchDown(object sender, TouchEventArgs e)
        {
            Keyboard.ClearFocus();
        }

        #region Dependency Properties
        public static DependencyProperty ClickedContentProperty = DependencyProperty.Register("ClickedContent", typeof(Object), typeof(ImageButton));
        public static DependencyProperty HighlightedContentProperty = DependencyProperty.Register("HighlightedContent", typeof(Object), typeof(ImageButton));

        public Object ClickedContent
        {
            get { return (Object)base.GetValue(ClickedContentProperty); }
            set { base.SetValue(ClickedContentProperty, value); }
        }

        public Object HighlightedContent
        {
            get { return (Object)base.GetValue(HighlightedContentProperty); }
            set { base.SetValue(HighlightedContentProperty, value); }
        }
        #endregion Dependency Properties
    }
}

XAML 资源字典 ImageButton.xaml:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:nqrgui="clr-namespace:NQR_GUI_WPF"
    >

    <Style TargetType="{x:Type nqrgui:ImageButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type nqrgui:ImageButton}">
                    <Grid>
                        <ContentControl
                            Content="{TemplateBinding Content}"
                            x:Name="PART_Content"
                            />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter 
                                TargetName="PART_Content" 
                                Property="Content" 
                                Value="{Binding HighlightedContent, RelativeSource={RelativeSource TemplatedParent}}" 
                                />
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter 
                                TargetName="PART_Content" 
                                Property="Content" 
                                Value="{Binding ClickedContent, RelativeSource={RelativeSource TemplatedParent}}" 
                                />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

下面是您将如何使用它:

<Window
    ...
    xmlns:nqrgui="clr-namespace:NQR_GUI_WPF"
    ...
    >

<!-- Or better yet, merge ImageButton.xaml in App.xaml so everybody can see it -->
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ImageButton.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

...

    <!-- As noted, Content, HighlightedContent, and ClickedContent 
    can be images -- or also paths, text, ANYTHING XAML can render.
    -->
    <nqrgui:ImageButton 
        Content="Content"
        HighlightedContent="Highlighted"
        ClickedContent="Clicked"
        />

你真的可以对内容发狂:

    <!-- Don't try this in a UI anybody will have to use! -->
    <nqrgui:ImageButton 
        Content="Content"
        ClickedContent="Clicked"
        >
        <nqrgui:ImageButton.HighlightedContent>
            <StackPanel Orientation="Horizontal">
                <Border 
                    BorderBrush="Gray" 
                    Background="GhostWhite" 
                    BorderThickness="1">
                    <Path 
                        Width="20" 
                        Height="20" 
                        Stroke="Black" 
                        StrokeThickness="2"
                        Data="M 0,0 L 20,20 M 0,20 L 20,0"
                        Margin="2"
                        />
                </Border>
                <nqrgui:ImageButton
                    Content="LOL"
                    ClickedContent="Don't Click Me, Bro!"
                    HighlightedContent="I heard you like buttons"
                    />
            </StackPanel>
        </nqrgui:ImageButton.HighlightedContent>
    </nqrgui:ImageButton>