Binding a Button in WPF MVVM

Scottintexas

Well-known member
Joined
Jun 21, 2018
Messages
47
Location
Texas
Programming Experience
5-10
I want to add a button to a row in a ListView. The row is simply several <TextBlocks> as defined in a <DataTemplate> in a <ResouceDictionary>. The command is in the ViewModel. I am missing a "connection" to the command in the ViewModel. If I add the button right to the View I can easily bind it to the code. But separating it to the ResourceDictionary makes the command invisible.


XAML in MainWindowView:
   <Window.Resources>
        <ResourceDictionary Source="./ViewResources.xaml" />
    </Window.Resources>

    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <ListView ItemsSource="{Binding Ports, Mode=OneWay}"
                                            ItemTemplate="{StaticResource ComportTemplate}"/>

ViewResources:
<DataTemplate x:Key="ComportTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Button Grid.Row="0"
                    Grid.Column="0"
                    Background="Red"
                    Height="10"
                    Width="10"
                    Margin="10"
                    VerticalAlignment="Center"
                    Command="{Binding TestPortCommand}"
                    CommandParameter="{Binding ElementName=PortName, Path=Text}"/>
                    
            <TextBlock x:Name="PortName"
                       Grid.Column="1"
                       VerticalAlignment="Center"
                       Margin="1,4,4,4">
            </TextBlock>
        </Grid>
    </DataTemplate>

Then in the MainWindowViewModel there is the code to handle the command.
MainWindowViewModel:
        private RelayCommand selectFilesCommand;
        private RelayCommand uploadCommand;
        private RelayCommand printFileCommand;
        private RelayCommand removeFileCommand;
        private RelayCommand testPortCommand;

        /// <summary>
        ///ICommand for the Select Files Button
        /// </summary>
        public ICommand SelectFilesCommand
        {
            get
            {
                if (selectFilesCommand == null)
                {
                    selectFilesCommand = new RelayCommand(param => SelectFiles());
                }
                return selectFilesCommand;
            }
        }

        /// <summary>
        /// Button to test communications with the serial port.
        /// </summary>
        /// <param name="port"></param>
        public ICommand TestPortCommand
        {
            get
            {
                if (testPortCommand == null)
                {
                    testPortCommand = new RelayCommand(param => TestComPort(param));
                }
                return testPortCommand;
            }
        }

A break point at the getter never gets hit. Unlike the SelectFilesCommand. That and the other buttons work great. But they are not being created in a DataTemplate.

Thanks for looking.
 
Solution
The problem was that the OnTestPort wasn't registered with the objects in the ListView. So I added it in the OnPortsListChanged and everything works as expected now. Thanks for your help.
You are comparing to your SelectFilesCommand, but you don't seem to show us how you are binding to it.
 
The select files command binding is in the MainWindowView, not in the resource dictionary.

C#:
<Button Content="Select Saved Files"
    Width="100"
    Margin="0,5,2,5"
    Command="{Binding SelectFilesCommand}"/>

But the key thing is that it is in the MainWindowView. If I put the test button in the MainWindowView, it would work just fine. It would look stupid because it doesn't belong out there just hanging around. It belongs to a specific SerialPort. So I put it in the list with the serial ports.
 
You'll need to move the TestPortCommand into the view model for the objects exposed by your Ports property. Recall that bindings are relative to the data context of the container. Since you are using a data template with the ListView, the bindings you use within that data template are relative to each object in the list. I know that WPF binding syntax lets you navigate down deeper, but I don't know if it was a way for you to navigate up and out of a container.
 
Thanks for your reply Skydiver. I rethought the button and moved it out of the ListView. Now it is with the other three buttons and set up the same exact way. I can step through the the button click of the other buttons, but this one button is not playing right. In the code below, two buttons will be shown, one that works and the Test button.

In the View:
         <Button Content="Test Port"
                    Style="{StaticResource TestButtonStyle}"
                    Command="{Binding TestPortCommand}"/>

            <Button Content="Start Test"
                        Margin="2"
                        Style="{StaticResource StartButtonStyle}"
                        Command="{Binding StartTestCommand}"/>


In the ViewModel:
        private void OnStartTest()
        {
            Logger.Info("Start Test has been clicked.");
            TestRunning = true;
            StartTest?.Invoke(this, EventArgs.Empty);
        }

        private void OnTestPort()
        {
            TestPortEventArgs TestPortArgs = new TestPortEventArgs
            {
                PortName = "Nothing"
            };
            TestPort?.Invoke(this, EventArgs.Empty);
        }

        public event EventHandler StartTest;
        public event EventHandler TestPort;

        private RelayCommand testPortCommand;
        private RelayCommand startTestCommand;

        public ICommand StartTestCommand
        {
            get
            {
                if (startTestCommand == null)
                {
                    startTestCommand = new RelayCommand(parm => OnStartTest());
                }
                return startTestCommand;
            }
        }
        
        public ICommand TestPortCommand
        {
            get
            {
                if (testPortCommand == null)
                {
                    testPortCommand = new RelayCommand(param => OnTestPort());
                }
                return testPortCommand;
            }
        }

Subscribed in another class:
        private void OnStartTest(object sender, EventArgs e)
        {
            Logger.Info("Start Test button has been clicked.");
            StartTest(sender as InstrumentViewModel);
        }

        public void OnTestPort(object sender, EventArgs e)
        {
            Logger.Info($"Executing port test command for . ");
            TestSerialPort(sender as InstrumentViewModel);
        }

When the user clicks the Start button everything works perfectly. There is a breakpoint set at the call to the StartTest method and the program halts there. When the user clicks the Test button, the break point set at the same place does not get hit. I also removed the style and tested it so there was no chance there was something there that would prevent it from working. Relay Command is the typical implementation.

I hope someone can see the glaring mistake. This kind of thing makes me crazy.
 
If you set breakpoints on lines 29 and 41 of your view model, do both breakpoints get hit?
 
If you set breakpoints on lines 29 and 41 of your view model, do both breakpoints get hit?
Only when the program starts up, which is how it should be. But I set a break points at line 5 and line 14. Both get hit but StartTest is an event with a target, TestPort is null. I just stepped through it again. Stopping at line 5 and examining the StartTest object, I can see Method, Target and Non-Public members. The target is defined as the place in the code called Subscribe in another class above for StartTest. TestPort remains null.
 
I guess that leads you to where you need to look next: Why is TestPort null?
 
The problem was that the OnTestPort wasn't registered with the objects in the ListView. So I added it in the OnPortsListChanged and everything works as expected now. Thanks for your help.
 
Solution
Back
Top Bottom