在 WPF UserControl 中附加 ICommand

Posted

技术标签:

【中文标题】在 WPF UserControl 中附加 ICommand【英文标题】:Attach ICommand in WPF UserControl 【发布时间】:2010-10-13 05:20:22 【问题描述】:

我实现了一个带有图像的简单按钮:

    <Button Command="Binding ButtonCommand, ElementName=ImageButtonControl">
        <StackPanel Orientation="Horizontal">
            <Image Source="Binding ButtonImage, ElementName=ImageButtonControl"/>
            <TextBlock Text="Binding ButtonText, ElementName=ImageButtonControl" Margin="2,0,0,0"/>
        </StackPanel>
    </Button>

如您所见,我公开了一个 ButtonCommand 属性,以便能够将 ICommand 附加到此 UserControl:

public partial class ImageButton : UserControl

    /// <summary>
    /// The dependency property that gets or sets the source of the image to render.
    /// </summary>
    public static DependencyProperty ImageSourceProperty = 
        DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(ImageButton));

    public static DependencyProperty TextProperty =
        DependencyProperty.Register("ButtonText", typeof(string), typeof(ImageButton));

    public static DependencyProperty ButtonCommandProperty =
        DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(ImageButton));

    public ImageButton()
    
        this.DataContext = this;
        InitializeComponent();
    

    /// <summary>
    /// Gets or sets the button command.
    /// </summary>
    public ICommand ButtonCommand
    
        get  return (ICommand)GetValue(ImageButton.ButtonCommandProperty); 
        set  SetValue(ImageButton.ButtonCommandProperty, value); 
    

    /// <summary>
    /// Gets or sets the button image.
    /// </summary>
    public ImageSource ButtonImage
    
        get  return (ImageSource)GetValue(ImageButton.ImageSourceProperty); 
        set  SetValue(ImageButton.ImageSourceProperty, value); 
    

    /// <summary>
    /// Gets or sets the button text.
    /// </summary>
    public string ButtonText
    
        get  return (string)GetValue(ImageButton.TextProperty); 
        set  SetValue(ImageButton.TextProperty, value); 
    

然后,当我声明我的按钮时,它会给出:

&lt;uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="Binding AttachContextCommand" ButtonImage="StaticResource AssociateImage" ButtonText="Associer"/&gt;

badaboom,当我点击我的 ImageButton 时,什么都不会发生。 当我用一个简单的按钮替换 ImageButton 时,会调用 ICommand。

我什至尝试简单地扩展 Button 类并绑定一个 ICommand,但又一次,它不起作用......

帮助赞赏!

谢谢。

【问题讨论】:

是按钮被禁用了,还是你点击后什么也没发生? 我点击它,命令永远不会被调用... 【参考方案1】:

您可以使用样式和几个附加属性以更简洁的方式实现此目的。

附加的属性将存储您的特定信息。 该样式将使用这些属性并构建您想要的外观。

该元素仍将是一个按钮,因此命令和其他一切都将起作用。

 public class ImageButton
    

        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(ImageButton), new UIPropertyMetadata(null));

        public static String GetCaption(DependencyObject obj)
        
            return (String)obj.GetValue(CaptionProperty);
        

        public static void SetCaption(DependencyObject obj, String value)
        
            obj.SetValue(CaptionProperty, value);
        

        public static readonly DependencyProperty CaptionProperty =
            DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(ImageButton), new UIPropertyMetadata(null));

    

<Style TargetType="x:Type Button"
               x:Key="ImageButton">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="Binding Path=(local:ImageButton.Image), RelativeSource=RelativeSource AncestorType=x:Type Button" />
                            <TextBlock Text="Binding Path=(local:ImageButton.Caption), RelativeSource=RelativeSource AncestorType=x:Type Button"
                                       Margin="2,0,0,0" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>   
        </Style>

然后您可以使用它来声明按钮:

   <Button Style="DynamicResource ImageButton"
                        local:ImageButton.Caption="Foo" 
                        local:ImageButton.Image="..." />

注意:

我很确定通过“模板”属性并使用 ControlTemplate 和 TemplateBindings 会更干净,但这意味着重新创建内容周围的边框和其他内容,所以如果您只想定义一个默认的“内容”,我认为我的例子就是要走的路。

【讨论】:

我也更喜欢这个答案。我通常不认为附加属性,但它们是 WPF 的一个很棒的功能。 确实如此。您可以通过附加属性将行为附加到元素(例如对诸如拖放之类的事件进行声明式处理),这一事实在您接触到它时会更加令人敬畏。 我有一个问题:是否有任何理由使用 DynamicResource? StaticResource 还不够吗? 在我对样式标记稍作更改后,这对我有用。由于路径异常,我不得不从 Image 和 TextBlock Binding 中删除“Path =”。现在我的 Image 元素看起来像:&lt;Image Source="Binding (local:ImageButton.Image), RelativeSource=RelativeSource AncestorType=x:Type Button" /&gt; 希望它有所帮助。【参考方案2】:

如果您想要为按钮添加的唯一功能是在其上添加图像,那么我认为您是从错误的方向来处理这个问题的。 WPF 之所以如此出色,是因为 UI 控件看起来不那么显眼。这意味着控件只是功能的定义 + 一些用于定义其外观的模板。这意味着可以随时更换模板以更改外观。此外,几乎任何内容都可以放在几乎任何控件中

例如,要在您的 xaml 中定义一个具有您所需要的外观的按钮是这样的:

<Window ...>
    ...
    <Button Command="Binding AttachContextCommand">
        <StackPanel Orientation="Horizontal">
            <Image Source="StaticResource AssociateImage"/>
            <TextBlock Text="Associer"/>
        </StackPanel>
    </Button>
    ...
</Window>

请记住,使用 WPF 时,您不必在每次想要更改某些外观和感觉时都定义新的 CustomControl 或 UserControl。唯一需要 CustomControl 的情况是您想向现有 Control 添加功能或创建任何其他 Control 中不存在的功能。

编辑由于评论:

如果您不想每次都为按钮定义内容,另一种选择是只使用一个 poco(普通的旧 CLR 对象)类来定义您感兴趣的所有内容(我将编写我的示例就好像您正在为工具栏执行此操作一样,因为这对我来说很有意义):

public class ToolBarItem : INotifyPropertyChanged

    public string Text  get ... set ... 
    public ICommand Command  get ... set ... 
    public ImageSource Image  get ... set ... 

在某处定义了一个数据模板(App.xaml、Window.Resources 等):

<DataTemplate DataType="x:Type l:ToolBarItem">
    <Button Command="Binding Command">
        <StackPanel Orientation="Horizontal">
            <Image Source="Binding Image"/>
            <TextBlock Text="Binding Text"/>
        </StackPanel>
    </Button>
</DataTemplate>

然后像这样在你的 xaml 中使用这个人:

<Window ...>
    ...
    <ContentControl>
        <ContentControl.Content>
            <l:ToolBarItem Image="..." Command="..." Text="..."/>
        </ContentControl.Content>
    </ContentControl>
    ...
</Window>

我只是不知道您尝试的方式是您可以做到的最 WPF 方式。

EDIT 根据第二条评论更新 抱歉,我忘了包括围绕它的 ContentControl。现在我想起了这一点,我意识到这并不比您手动指定内容的原始版本少多少。我将发布一个新答案来帮助您解决原来的问题。

【讨论】:

嗨,我知道。实际上,创建用户控件的目的是避免为所有按钮编写此控件。这样做,我只需要写: 如果你愿意,它只是重构。 抱歉,这不起作用... ToolBarItem 不是 UIElement,当我尝试启动应用程序时,我收到一个异常提示。【参考方案3】:

重新回答原来的问题:

我认为您想要做的是创建一个名为 ImageButton 的新 CustomControl。然后将其更改为从 Button 而不是 Control 扩展。您不需要 Command 属性,因为 Button 已经有一个。您只需要添加一个 Image 属性,就可以重用按钮的 Content 属性,而不是 Text 属性。

创建 CustomControl 后,它会在 Generic.xaml 中为 ImageButton 的默认样式添加一个条目。在 Template 属性的 Setter 中,您可以将 ControlTemplate 更改为:

<ControlTemplate TargetType="x:Type local:ImageButton">
    <StackPanel Orientation="Horizontal">
        <Image Source="TemplateBinding Image"/>
        <ContentPresenter/>
    </StackPanel>
</ControlTemplate>

然后,当你想使用它时:

<Window ... >
...
    <l:ImageButton Image="StaticResource ..." Command="...">
        Associer
    </l:ImageButton>
...
</Window>

【讨论】:

【参考方案4】:

内置 WPF 按钮包含触发附加命令以响应单击的代码。您的“ImageButton”派生自 UserControl,因此您不会得到这种行为。获得所需内容的最短路径可能是让 ImageButton 类实际派生自 WPF Button 类。为此,请将 ImageButton 的标记从

<UserControl
...
>
...
</UserControl>

<Button
...
>
...
</Button>

然后将ImageButton的基类从UserControl改为Button

在一切正常之前,您可能需要进行一些其他的小改动。

【讨论】:

你好丹尼尔,我确实试图扩展按钮类,但命令也从未被调用......

以上是关于在 WPF UserControl 中附加 ICommand的主要内容,如果未能解决你的问题,请参考以下文章

2016-11-11坚持学习Day24WPF 自定义控件 附加属性 自定义事件

WPF程序中treeview图标显示不全

在WPF中UserControl

WPF中UserControl控件的关闭

WPF 导航和销毁当前 UserControl

UserControl在wpf中使用父元素?