C# 在 WPF UserControl 中附加 ICommand

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/643689/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 11:31:12  来源:igfitidea点击:

Attach ICommand in WPF UserControl

c#wpfmvvmuser-controls

提问by Roubachof

I implemented a simple button with an image in it:

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

    <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>

As you can see, I expose a ButtonCommand property in order to be able to attach an ICommand to this UserControl:

如您所见,我公开了一个 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); }
    }
}

Then when I declare my button it gives this:

然后当我声明我的按钮时,它给出了这个:

<uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" ButtonText="Associer"/>

<uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" ButtonText="Associer"/>

And badaboom, nothing never happen when I click on my ImageButton. When I replace the ImageButton with a simple button, the ICommand is called.

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

I even tried to simply extends the Button class and bind an ICommand, but once again, it didn't work...

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

Help appreciated !

帮助表示赞赏!

Thx.

谢谢。

采纳答案by Denis Troller

You can achieve this in a much cleaner way using a style and a couple of attached properties.

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

The attached properties will store your specific information. The style will use these properties and build the look you want.

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

The element will still be a button so the command and everything else will work.

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

 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>

You can then use this to declare buttons:

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

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

Note:

笔记:

I'm pretty sure it would be cleaner to go through the "Template" property and use a ControlTemplate and TemplateBindings, but that would mean re-creating the border and other stuff around your content, so if you are looking to just define a default "Content", my example would be the way to go, I think.

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

回答by Daniel Pratt

The built-in WPF button contains code that fires the attached command in response to being clicked. Your "ImageButton" derives from UserControl, so you don't get that behavior. Probably the shortest route to get what you want is for your ImageButton class to actually derive from the WPF Button class. To accomplish that, change the markup for ImageButton from

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

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

to

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

Then change the base class of ImageButton from UserControlto Button.

然后将 ImageButton 的基类从 更改UserControlButton

You'll probably need to make some other minor changes before it all works.

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

回答by dustyburwell

If the only added functionality that you want for your button is to have an image on it, then I think you're approaching this from the wrong direction. WPF is as awesome as it is because the UI controls are look-less.This means that a Control is merely a definition of functionality + some template to define how it looks. This means that the template can be swapped out at any time to change the look. Also, almost any content can be placed inside of almost any control

如果您想要为按钮添加的唯一功能是在其上添加图像,那么我认为您是从错误的方向接近这一点。WPF 非常棒,因为 UI 控件看起来很少。这意味着 Control 只是功能的定义 + 一些模板来定义它的外观。这意味着可以随时更换模板以更改外观。此外,几乎任何内容都可以放置在几乎任何控件中

For instance, to define a button in your xaml that has the look your going for all you need is this:

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

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

Just keep in mind that with WPF you don't have to define a new CustomControl or UserControl every time you want to change the look and feel of something. The only time you should need a CustomControl is if you want to add functionality to an existing Control or to create functionality that doesn't exist in any other Control.

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

Edit Due to comment:

编辑由于评论:

If you're wanting to keep from defining the content for the button every time, the other option is to just have a poco (plain old CLR object) class that would define everything your interested in (I'll write my example as if you're doing this for a tool bar, because it makes sense to me):

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

public class ToolBarItem : INotifyPropertyChanged
{
    public string Text { get ... set ... }
    public ICommand Command { get ... set ... }
    public ImageSource Image { get ... set ... }
}

That has a data template defined somewhere (App.xaml, Window.Resources, etc):

在某处定义了一个数据模板(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>

And then use the guy in your xaml like this:

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

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

I just don't know that the way you're trying to do it is the most WPF way you could do it.

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

EDIT Updated based on second commentSorry, I forgot to include the ContentControl surrounding that. Now that I remembered that, I realize that that's not much less verbose than the original where you are specifying the content manually. I'll post a new answer to help with your original question.

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

回答by dustyburwell

To re-answer the original question:

重新回答原来的问题:

What I think you want to do is create a new CustomControl called ImageButton. Then change it to extend from Button instead of Control. You won't need a Command property since Button already has one. You'll only need to add an Image property and you can reuse the Content property from button instead of having a Text property.

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

When your CustomControl is created, it'll add an entry in your Generic.xaml for the default style of your ImageButton. In the Setter for the Template property you can change the ControlTemplate to this:

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

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

Then, again, when you want to use it:

然后,再次,当你想使用它时:

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