DelusionX
DelusionX

Reputation: 79

Bind Button.CommandParameter to MVVM object to use the same ICommand to different Buttons

I have quite a hard time to figure out the solution to a rather trivial problem of mine.

I have three buttons that execute the same command (same function call). The only thing that changes is that the function call affects different objects per button. So instead of creating the button command 3 times I just created Command parameters that I bind to different MVVM objects. My code below will clear things up.

My problem is that I don't know how to bind a command parameter to an MVVM object. Towards this direction I get back the following error

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

XAML code to bind the elements together (I have added also the MultiBinding.Converter method as proposed in the submitted answer by Peter)

XAML file

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

The binding elements like FilesFilePath, EnableFilesBrowseButton, etc are MVVM objects that bind to other XAML objects and get affected by the ICommand.

Now I present to you the c# code with the MVVM objects and ICommand function: .cs file

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));
        }
    }
}

Note: Inside the ICommand function BrowseButtonCommand I call a method named BrowseFile. Don't pay attention to that because the function I present works perfectly.

My goal and concern is how can I bind the command parameters with the MVVM objects and adopt their values. How to pass a command.parameter value to an MVVM object. For example, I want the Command Parameter FilePathSelected to get the value of the MVVM object FilesFilePath

And then when I use the same command for a different browse button BrowseButton2 I should be able to provide the values of the MVVM objects of my second Browse Button like

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

Do you know how can I achieve this? I think I am on the correct path, although it's clear that I miss something. I have created my MVVM objects, my ICommand function, although I want to bind them together so the one to take the value of the other. As I mentioned I need to do this for three Browse Buttons (BrowseButton1, BrowseButton2, BrowseButton3). Thus, I don't want to create three ICommands but rather only one.

I am more than willing to share with you more info about my question in case there is something misunderstood.

Upvotes: 0

Views: 877

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70671

You didn't provide a minimal, complete code example, so it's impossible to know for sure. But based on the error message, I would expect that the BrowseButtonCommandParameters is not a dependency object, and thus cannot be the target for any binding.

Most likely, what you need to do is use an IMultiValueConverter to copy the source values into an instance of BrowseButtonCommandParameters for your command. For example:

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");
    }
}

Then in your XAML, you can do something like this:

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

Upvotes: 2

Related Questions