C
C#2y ago
frostfina

Passing a string property to a command via a button CommandParameter in .NET 6

Hello So I'm trying to pass a string (that's bound to a text field via a data context <TextBox [...] Text="{Binding SearchString}"/> which I made sure already works) using a button's CommandParameter. The relevant button code is as follows (I've hidden layout arguments)
<Button [...] Command="{Binding PerformSearch}" CommandParameter="{Binding SearchString}" />
<Button [...] Command="{Binding PerformSearch}" CommandParameter="{Binding SearchString}" />
PerformSearch is a property of the class MainViewModel that I have defined as a custom class
internal class SearchCommand : ICommand
{
public event EventHandler? CanExecuteChanged;

public bool CanExecute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("CanExecute(parameter) : parameter=" + parameter);
return true;
}

public void Execute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("Execute(parameter) : parameter=" + parameter);
}
}
//...
internal class MainViewModel : INotifyPropertyChanged
{
// ...
public ICommand? PerformSearch { get; } = new SearchCommand();
// ...
}
internal class SearchCommand : ICommand
{
public event EventHandler? CanExecuteChanged;

public bool CanExecute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("CanExecute(parameter) : parameter=" + parameter);
return true;
}

public void Execute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("Execute(parameter) : parameter=" + parameter);
}
}
//...
internal class MainViewModel : INotifyPropertyChanged
{
// ...
public ICommand? PerformSearch { get; } = new SearchCommand();
// ...
}
This does however not work, pressing the button does call the command and I do see
CanExecute(parameter) : parameter=
Execute(parameter) : parameter=
CanExecute(parameter) : parameter=
Execute(parameter) : parameter=
in the program's output, however after testing parameter is null. If I pass a string directly instead of using a binding, I do get the string as an output
<Button [...] Command="{Binding PerformSearch}" CommandParameter="Hello" />
<Button [...] Command="{Binding PerformSearch}" CommandParameter="Hello" />
which produces
CanExecute(parameter) : parameter=Hello
Execute(parameter) : parameter=Hello
CanExecute(parameter) : parameter=Hello
Execute(parameter) : parameter=Hello
What am I doing wrong with my previous binding? Maybe this is the wrong way of handling button presses? I've seen people just use CommandParameter="Binding" and pass the whole ViewModel, and it does indeed pass the whole viewmodel, but I feel like it defeats the purpose of having a separate class handle the command.
14 Replies
canton7
canton72y ago
The binding failed for some reason, most likely. Maybe that property doesn't exist, or it's a field, or it had the value null when the binding accessed it, etc?
frostfina
frostfina2y ago
I initialized it like this so I doubt it
private string _searchString = "";
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
OnPropertyChanged(SearchString);
}
}
private string _searchString = "";
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
OnPropertyChanged(SearchString);
}
}
But yeah I assumed it was a binding error
canton7
canton72y ago
The obvious thing is that it has the value "" there? Try turning the binding debug level up, see what's happening
frostfina
frostfina2y ago
Yes but if it's set to "" why is it null
canton7
canton72y ago
Well, in fairness the output doesn't say whether it's null or ""
frostfina
frostfina2y ago
I did add an if condition earlier that tested that, and it was null, but I removed it But true If I programatically set _searchString to some value, it works the first time, but then it doesn't get the new values. I think it gets a reference to the first string instance, then once it changes it becomes null. I guess I can't bind to a property like this since it gets a new instance
canton7
canton72y ago
That's expected, as when you set _searchString directly then the PropertyChanged event won't be fired, and the binding won't know that anything's changed You shouln't get null there -- the binding just won't update
frostfina
frostfina2y ago
I only change SearchString through a data binding <TextBox [...] Text="{Binding SearchString}"/>
canton7
canton72y ago
Aha Looks like the CommandParameter property doesn't set the default binding mode to OneWay, so if you have two read/write bindings... And I can't remember what happens in that case Try CommandParameter="{Binding SearchString, Mode=OneWay}" ? And only ever set SearchString in code, not _searchString
frostfina
frostfina2y ago
Same as before, actually I noticed that whatever I set _searchString to first, it just keeps that value for any subsequent call, with or without OneWay Now however what I don't understand is that it didn't do that before, I must've changed something I thought was irrelevant and now it acts differently ;_;
canton7
canton72y ago
Can you share your actual test code and its outputs?
frostfina
frostfina2y ago
MainModelView.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace MeteoApp
{
internal class SearchCommand : ICommand
{
public event EventHandler? CanExecuteChanged;

public bool CanExecute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("CanExecute(parameter) : parameter=" + parameter);
return true;
}

public void Execute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("Execute(parameter) : parameter=" + parameter);
}
}

internal class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

public ICommand? PerformSearch { get; } = new SearchCommand();
private string _searchString = "";
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
OnPropertyChanged(SearchString);
}
}

public MainViewModel()
{
}

public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace MeteoApp
{
internal class SearchCommand : ICommand
{
public event EventHandler? CanExecuteChanged;

public bool CanExecute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("CanExecute(parameter) : parameter=" + parameter);
return true;
}

public void Execute(object? parameter)
{
System.Diagnostics.Debug.WriteLine("Execute(parameter) : parameter=" + parameter);
}
}

internal class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;

public ICommand? PerformSearch { get; } = new SearchCommand();
private string _searchString = "";
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
OnPropertyChanged(SearchString);
}
}

public MainViewModel()
{
}

public void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
MainView.xaml
<UserControl x:Class="MeteoApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MeteoApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local:MainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" Header="Search">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Location"/>
<TextBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Text="{Binding SearchString}"/>
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="Search" Command="{Binding PerformSearch}" CommandParameter="{Binding SearchString, Mode=OneWay}" />

</Grid>
</GroupBox>

<GroupBox Grid.Column="1" Header="Results"/>
</Grid>
</UserControl>
<UserControl x:Class="MeteoApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MeteoApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<local:MainViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" Header="Search">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Content="Location"/>
<TextBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Text="{Binding SearchString}"/>
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="Search" Command="{Binding PerformSearch}" CommandParameter="{Binding SearchString, Mode=OneWay}" />

</Grid>
</GroupBox>

<GroupBox Grid.Column="1" Header="Results"/>
</Grid>
</UserControl>
I just press the "Search" button to do my testing after changing the text in the TextBox Dunno if it's relevant, but here's the MainWindow's XAML as well
<Window x:Class="MeteoApp.MainWindow"
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:MeteoApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MainView x:Key="MainView"/>
</Window.Resources>
<Grid>
<ContentControl Content="{StaticResource MainView}"/>
</Grid>
</Window>
<Window x:Class="MeteoApp.MainWindow"
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:MeteoApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MainView x:Key="MainView"/>
</Window.Resources>
<Grid>
<ContentControl Content="{StaticResource MainView}"/>
</Grid>
</Window>
canton7
canton72y ago
That sort of thing should work -- I've done it plenty of times myself
frostfina
frostfina2y ago
Yeah that's why I'm surprised that fails I mean I'm very knew to .NET and I've ever only had school experience, I'm far more experienced in other frameworks like Qt and it's pretty annoying to be stuck on such a simple problem. If I figure out the solution I'll make sure to share it