【问题标题】:Bind Button.CommandParameter to MVVM object to use the same ICommand to different Buttons将 Button.CommandParameter 绑定到 MVVM 对象以使用相同的 ICommand 到不同的按钮
【发布时间】:2021-02-20 21:46:14
【问题描述】:

我很难找到解决我的一个相当微不足道的问题的方法。

我有三个按钮执行相同的命令(相同的函数调用)。唯一改变的是函数调用会影响每个按钮的不同对象。因此,我没有创建 3 次按钮命令,而是创建了绑定到不同 MVVM 对象的命令参数。我下面的代码将清除一切。

我的问题是我不知道如何将命令参数绑定到 MVVM 对象。朝着这个方向我得到了以下错误

Object of type 'System.Windows.Data.Binding cannot be converted to 'System.Boolean' //or System.String

将元素绑定在一起的 XAML 代码(我还添加了 Peter 在提交的答案中提出的 MultiBinding.Converter 方法)

XAML 文件

<Window x:Class="TestEnvironment.MainWIndowTestStackOverflow"
        x:Name="MainWindowTestStackOverflow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestEnvironment"
        mc:Ignorable="d"
        Height="720"
        Width="1145"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen"
        BorderBrush="Black"
        BorderThickness="1.5,1.5,1.5,1.5"
        WindowStyle="None">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid x:Name="GridMain"
         Width="1145"
         Background="White"
         HorizontalAlignment="Center"
         ShowGridLines="False" 
         Grid.Row="1">
        <!--Grid Columns-->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0"/>
            <ColumnDefinition Width="195"/>
            <ColumnDefinition Width="295"/>
            <ColumnDefinition Width="650"/>
            <ColumnDefinition Width="0"/>
        </Grid.ColumnDefinitions>
        <!--Grid Rows-->
        <Grid.RowDefinitions>
            <RowDefinition Height="0"/>
            <RowDefinition Height="45"/>
            <RowDefinition Height="45"/>
            <RowDefinition Height="45"/>
            <RowDefinition Height="45"/>
            <RowDefinition Height="52"/>
            <RowDefinition Height="400"/>
        </Grid.RowDefinitions>
        <TextBox
            Name="FileNameTextBox"
            Text="{Binding Path=FilesFilePath}"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Margin="5,0,0,0"
            IsReadOnly="True"
            FontStyle="Italic"
            FontFamily="Arial"
            FontSize="9"
            BorderThickness="0"
            Grid.Column="2"
            Grid.Row="1"/>
        <!--Apply ICommand to browse file 1st time-->
        <Button 
            x:Name="BrowseButton1"
            Content="Browse"
            Command="{Binding Path=BrowseButtonCommand}"
            IsEnabled="{Binding Path=EnableFilesBrowseButton, Converter={StaticResource BooleanToVisibilityConverter}}"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Width="80" 
            Height="25"
            Margin="40,0,0,0"
            Padding="0"
            FontWeight="Light"
            FontSize="10"
            Grid.Column="3"
            Grid.Row="1"
            Cursor="Hand">
            <Button.CommandParameter>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <local:BrowseButtonConverter/>
                    </MultiBinding.Converter>
                    <Binding Path="FilesFilePath"/>
                    <Binding Path="EnableFilesBrowseButton"/>
                    <Binding Path="EnableFilesLoadButton"/>
                    <Binding Path="EnableFilesViewButton"/>
                    <Binding Path="FilesPanelVisibility"/>
                </MultiBinding>
            </Button.CommandParameter>
        </Button>
        <Button 
            x:Name="LoadButton1"
            Content="Load"
            IsEnabled="{Binding Path=EnableFilesLoadButton, Converter={StaticResource BooleanToVisibilityConverter}}"
            Focusable="False"
            Width="80"
            Height="25"
            Margin="135,0,0,0"
            FontSize="10"
            FontWeight="Light"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Grid.Column="3"
            Grid.Row="1"
            Cursor="Hand">
        </Button>
        <StackPanel
            x:Name="StackPanelFiles"
            VerticalAlignment="Center"
            HorizontalAlignment="Left"
            Grid.Row="1"
            Grid.Column="3"
            Orientation="Vertical"
            Width="400"
            Height="36"
            Margin="235,0,0,0"
            Visibility="{Binding XPath=FilesPanelVisibility, Converter={StaticResource BooleanToVisibilityConverter}}">
        </StackPanel>
        <Button
            x:Name="PreviewButton1"
            IsEnabled="{Binding XPath=EnableFilesViewButton, Converter={StaticResource BooleanToVisibilityConverter}}"
            Focusable="False"
            Content="View"
            Width="80"
            Height="25"
            Margin="545,0,0,0"
            FontSize="10"
            FontWeight="Light"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Grid.Column="3"
            Grid.Row="1"
            Cursor="Hand">
        </Button>
        <!--Apply ICommand to browse file 2nd time-->
        <TextBox
            Name="FileNameTextBoxLayout"
            Text="{Binding Path=LayoutFilePath}"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Height="25"
            Margin="5,6,0,0"
            IsReadOnly="True"
            FontStyle="Italic"
            FontFamily="Arial"
            FontSize="9"
            BorderThickness="0"
            Grid.Column="2" 
            Grid.Row="3"/>
        <Button 
            x:Name="BrowseButton2"
            Content="Browse"
            Command="{Binding Path=BrowseButtonCommand}"
            IsEnabled="{Binding Path=EnableLayoutBrowseButton, Converter={StaticResource BooleanToVisibilityConverter}}"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Width="80" 
            Height="25"
            Margin="40,0,0,0"
            Padding="0"
            FontWeight="Light"
            FontSize="10"
            Grid.Column="3"
            Grid.Row="2"
            Cursor="Hand">
            <Button.CommandParameter>
                <MultiBinding>
                    <MultiBinding.Converter>
                        <local:BrowseButtonConverter/>
                    </MultiBinding.Converter>
                    <Binding Path="LayoutFilePath"/>
                    <Binding Path="EnableLayoutBrowseButton"/>
                    <Binding Path="EnableLayoutLoadButton"/>
                    <Binding Path="EnableLayoutViewButton"/>
                    <Binding Path="LayoutPanelVisibility"/>
                </MultiBinding>
            </Button.CommandParameter>
        </Button>
        <Button 
            x:Name="LoadButton2"
            Content="Load"
            IsEnabled="{Binding Path=EnableLayoutLoadButton, Converter={StaticResource BooleanToVisibilityConverter}}"
            Focusable="False"
            Width="80"
            Height="25"
            Margin="135,0,0,0"
            FontSize="10"
            FontWeight="Light"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Grid.Column="3"
            Grid.Row="2"
            Cursor="Hand">
        </Button>
        <!--...The rest of the controls as created above but with different names-->
    </Grid>
</Window>

FilesFilePathEnableFilesBrowseButton 等绑定元素是绑定到其他 XAML 对象并受 ICommand 影响的 MVVM 对象。

现在我向您展示带有 MVVM 对象和 ICommand 函数的 c# 代码: .cs 文件

namespace TestEnvironment
{
    //Command parameters
    public class BrowseButtonCommandParameters
    {
        public string FilePathSelected { get; set; } //parameter 1
        public bool EnableBrowseButton { get; set; } //parameter 2
        public bool EnableLoadButton { get; set; } //parameter 3
        public bool EnableViewButton { get; set; } //parameter 4
        public bool VisibilityPanel { get; set; } //parameter 5
    }
    //Browse - MultiValueConverter
    class BrowseButtonConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // Error handling omitted for brevity
            // Casting omitted because question's code has insufficient context
            return new BrowseButtonCommandParameters
            {
                FilePathSelected = (string)values[0],
                EnableBrowseButton = (bool)values[1],
                EnableLoadButton = (bool)values[2],
                EnableViewButton = (bool)values[3],
                VisibilityPanel = (bool)values[4],
            };
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException("to-source binding mode not supported");
        }
    }
    
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        //----------------------------------------------------------------------------------------------------
        //MVVM objects binded to xaml objects and connected to ICommand parameters

        //Used by BrowseButton1
        //1.
        private bool _enableFilesLoadButton;
        public bool EnableFilesLoadButton
        {
            get
            {
                return _enableFilesLoadButton;
            }
            set
            {
                _enableFilesLoadButton = value;
                OnPropertyChanged("EnableFilesLoadButton");
            }
        }

        //2.
        private bool _enableFilesBrowseButton;
        public bool EnableFilesBrowseButton
        {
            get
            {
                return _enableFilesBrowseButton;
            }
            set
            {
                _enableFilesBrowseButton = value;
                OnPropertyChanged("EnableFilesBrowseButton");
            }
        }

        //3.
        private bool _enableFilesViewButton;
        public bool EnableFilesViewButton
        {
            get
            {
                return _enableFilesViewButton;
            }
            set
            {
                _enableFilesViewButton = value;
                OnPropertyChanged("EnableFilesViewButton");
            }
        }

        //4.
        private string _FilesFilePath;
        public string FilesFilePath
        {
            get
            {
                return _FilesFilePath;
            }
            set
            {
                _FilesFilePath = value;
                OnPropertyChanged("FilesFilePath");
            }
        }

        //5.
        private bool _changeFilesPanelVisibility;
        public bool FilesPanelVisibility
        {
            get
            {
                return _changeFilesPanelVisibility;
            }
            set
            {
                _changeFilesPanelVisibility= value;
                OnPropertyChanged("FilesPanelVisibility");
            }
        }

        //Used by BrowseButton2
        //1.
        private bool _enableLayoutLoadButton;
        public bool EnableLayoutLoadButton
        {
            get
            {
                return _enableLayoutLoadButton;
            }
            set
            {
                _enableLayoutLoadButton = value;
                OnPropertyChanged("EnableLayoutLoadButton");
            }
        }

        //2.
        private bool _enableLayoutBrowseButton;
        public bool EnableLayoutBrowseButton
        {
            get
            {
                return _enableLayoutBrowseButton;
            }
            set
            {
                _enableLayoutBrowseButton = value;
                OnPropertyChanged("EnableLayoutBrowseButton");
            }
        }

        //3.
        private bool _enableLayoutViewButton;
        public bool EnableLayoutViewButton
        {
            get
            {
                return _enableLayoutViewButton;
            }
            set
            {
                _enableLayoutViewButton = value;
                OnPropertyChanged("EnableLayoutViewButton");
            }
        }

        //4.
        private string _LayoutFilePath;
        public string LayoutFilePath
        {
            get
            {
                return _LayoutFilePath;
            }
            set
            {
                _LayoutFilePath = value;
                OnPropertyChanged("LayoutFilePath");
            }
        }

        //5.
        private bool _changeLayoutPanelVisibility;
        public bool LayoutPanelVisibility
        {
            get
            {
                return _changeLayoutPanelVisibility;
            }
            set
            {
                _changeLayoutPanelVisibility= value;
                OnPropertyChanged("LayoutPanelVisibility");
            }
        }


        //----------------------------------------------------------------------------------------------------
        //ICommand: BrowseButtonCommand
        public ICommand BrowseButtonCommand
        {
            get { return new DelegateCommand<object>(FuncBrowseCommand); }
        }
        public void FuncBrowseCommand(object parameters)
        {
            var param = (BrowseButtonCommandParameters)parameters;

            //Load button gets instantly disabled when every time the user clicks the Browse Button 
            param.EnableLoadButton = false;

            Nullable<bool> browse_result = BrowseFile(param.FilePathSelected);

            //Browse file
            if (browse_result == true)
            {
                param.EnableBrowseButton = true;
                param.EnableLoadButton = true;
                param.EnableViewButton = false;
                param.VisibilityPanel = false;
            }
            else
            {
                return;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string property)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}

注意:在 ICommand 函数 BrowseButtonCommand 内部,我调用了一个名为 BrowseFile 的方法。不要太在意,因为我介绍的功能完美无缺。

我的目标和关注点是如何将命令参数与 MVVM 对象绑定并采用它们的值。如何将 command.parameter 值传递给 MVVM 对象。比如我想让命令参数FilePathSelected得到MVVM对象FilesFilePath的值

然后,当我对不同的浏览按钮 BrowseButton2 使用相同的命令时,我应该能够提供我的第二个浏览按钮的 MVVM 对象的值,例如

<Button 
    x:Name="BrowseButton2"
    Content="Browse"
    Command="{Binding Path=BrowseButtonCommand}">
    <Button.CommandParameter> //Parameters that share value with MVVM objects of the second Browse Button
        <local:BrowseButtonCommandParameters FilePathSelected="{Binding Path=LayoutFilePath}" //those return the error I mentioned earlier.
                                             EnableBrowseButton="{Binding Path=EnableLayoutBrowseButton}"
                                             EnableLoadButton="{Binding Path=EnableLayoutLoadButton}"
                                             EnableViewButton="{Binding Path=EnableLayoutViewButton}"
                                             VisibilityPanel="{Binding Path=LayoutPanelVisibility}"/>
    </Button.CommandParameter>
</Button>

你知道我怎样才能做到这一点吗?我认为我走在正确的道路上,尽管很明显我错过了一些东西。我已经创建了我的 MVVM 对象,我的 ICommand 函数,尽管我想将它们绑定在一起,以便一个取另一个的值。正如我提到的,我需要为三个浏览按钮(BrowseButton1BrowseButton2BrowseButton3)执行此操作。因此,我不想创建三个 ICommand,而是只创建一个。

我非常愿意与您分享有关我的问题的更多信息,以防出现误解。

【问题讨论】:

    标签: c# wpf xaml mvvm


    【解决方案1】:

    您没有提供a minimal, complete code example,因此无法确定。但根据错误消息,我认为BrowseButtonCommandParameters 不是依赖对象,因此不能成为任何绑定的目标。

    很可能,您需要做的是使用IMultiValueConverter 将源值复制到BrowseButtonCommandParameters 的实例中以供您的命令使用。例如:

    class BrowseButtonConverter : IMultiValueConverter
    {
        public object Convert (object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // Error handling omitted for brevity
            // Casting omitted because question's code has insufficient context
            return new BrowseButtonCommandParameters
            {
                FilePathSelected = values[0],
                EnableBrowseButton = values[1],
                EnableLoadButton = values[2],
                EnableViewButton = values[3],
                VisibilityPanel = values[4],
            };
        }
    
        public object[] ConvertBack (object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException("to-source binding mode not supported");
        }
    }
    

    然后在您的 XAML 中,您可以执行以下操作:

    <Button  x:Name="BrowseButton1"
             Content="Browse"
             Command="{Binding Path=BrowseButtonCommand}">
      <Button.CommandParameter>
        <MultiBinding>
          <MultiBinding.Converter>
            <local:BrowseButtonConverter/>
          </MultiBinding.Converter>
          <Binding Path="FilesFilePath"/>
          <Binding Path="EnableFilesBrowseButton"/>
          <Binding Path="EnableFilesLoadButton"/>
          <Binding Path="EnableFilesViewButton"/>
          <Binding Path="FilesPanelVisibility"/>
        </MultiBinding>
      </Button.CommandParameter>
    </Button>
    

    【讨论】:

    • 彼得感谢您的回答。我会测试它并让你知道结果。正如您正确理解的那样(根据您的回答)。我有 5 个用于 BrowseButton1 的 MVVM 对象,以及 5 个用于 BrowseButton2 的另一个 MVVM 对象(目的相同但名称不同)。两个浏览按钮具有完全相同的功能。因此,我不想制作 2 个 ICommand,而是将 MVVM 对象作为 Command.Parameters 传递并更改 UI ..
    • 彼得对您的回答发表评论。当我将它插入我的代码时,它会返回关于 value 参数的类型错误。由于我使用布尔值和字符串,我无法将它们设置为对象。根据您的回答,values 设置为类型object。但是,在我的代码中,FilePathSelected 是一个字符串。在我使用 FilePathSelected=(string)values[0] 之后,我的 xaml UI 消失了
    • "在我的代码中 FilePathSelected 是一个字符串" -- 然后你需要转换。您说您尝试过,但请注意,在尚未设置数据上下文的情况下可能会调用转换器。您需要在转换器中添加代码来检查这些值,并且只有在您可以成功执行此操作时才返回初始化的BrowseButtonCommandParameters 对象。当转换器返回无效对象(例如,您可以使用nullDependencyObject.UnsetValue)时,请考虑禁用命令(即让CanExecute() 返回false)。正如我在回答中指出的那样,您没有提供...
    • ...minimal reproducible example,因此不可能在保证有效的答案中发布代码。您需要在答案中采用通用方法并花时间理解它,以便您可以将该通用方法应用于您的特定场景。
    • Peter 我已经根据您的 asnwer 和我在 sodocumentation.net/wpf/topic/3950/… 找到的关于 Null 检查的一些附加代码编辑了我的问题。所以基本上,你的解决方案有效。尽管如此,当我对不同的 ICommand 使用相同的命令参数时,我得到一个空引用错误。这超出了问题的范围,所以我将创建一个新的。例如,在我的问题中,我有 Browse 命令。现在想象一下我也有加载命令(用于加载按钮)。两个 ICommand 共享 FilesFilePath
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-05
    • 2013-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-02
    • 2011-05-09
    相关资源
    最近更新 更多