My project uses CommunityToolkit.Mvvm8.0.
I use the [RelayCommand] attributeto create a method to generate the command.
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/overview
Why is Click working fine but OnSelectionChanged not working?
Code:
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding FirstName}"/>
<Button Content="Click Me" Command="{Binding OnSelectionChangedCommand}"/>
<Button Content="Click Me" Command="{Binding ClickCommand}"/>
</StackPanel>
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public partial class ViewModel : ObservableObject
{
[ObservableProperty]
private string firstName = "Kevin";
public ViewModel()
{
}
[RelayCommand]
private void OnSelectionChanged()
{
FirstName = "David";
}
[RelayCommand]
private void Click()
{
FirstName = "David";
}
}
According to RelayCommand attribute, "On" at the head of method name will be removed from auto-generated command.
The generator will use the method name and append "Command" at the end, and it will strip the "On" prefix, if present.
Thus, the name of Command will be SelectionChangedCommand.
When you decorate a method with the RelayCommandAttribute, an ICommand property is generated and the name of that generated property will be method name with "Command" appended at the end.
As the docs clearly says, the "On" prefix will also be stripped from the generated property name.
So your sample code works just fine if you simply remove the "On" part from the XAML markup as there is no generated command named "OnSelectionChangedCommand":
<Button Content="Click Me" Command="{Binding SelectionChangedCommand}"/>
Related
I'm using WinUI MVVM (as an MVVM newbie)
Here is my button in XAML
<Button Grid.Row="0" Content="Create New" Width="100" Margin="5"
Command="{x:Bind ViewModel.CreateNewCommand }"
Visibility="{x:Bind ViewModel.IsCreateNewVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
In the constructor of the ViewModel I have this connection:
CreateNewCommand = new RelayCommand(Handle_CreateNewCommand, CanExecuteCreateNew);
and here is the CanExecute method:
public bool CanExecuteCreateNew()
{
return IsCreateNewEnabled;
}
private bool _isCreateNewEnabled = false;
public bool IsCreateNewEnabled
{
get => _isCreateNewEnabled;
set
{
SetProperty(ref _isCreateNewEnabled, value);
}
}
If I assign the IsCreateNewEnabled property in the VM constructor it renders correctly, enabled or disabled.
When I click the button it fires the handler method and before a single line of code in that method is executed, it fires the canExecute method with a value of true. In the handler method I set IsCreateNewEnabled = false but that has no effect on the button and doesn't fire the CanExecute method.
Any ideas?
Thanks
Carl
You can do it this way with the CommunityToolkit.Mvvm NuGet package.
ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Mvvm;
// This class needs to be "partial" for the CommunityToolkit.Mvvm.
public partial class YourViewModel : ObservableObject
{
[RelayCommand(CanExecute = nameof(CanCreateNew))]
// A "CreateNewCommand" command will be auto-generated.
private void CreateNew()
{
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateNewCommand))]
// A "CanCreateNew" property that
// notifies the "CreateNewCommand" to update its availability
// will be auto-generated.
private bool canCreateNew = false;
}
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
namespace Mvvm;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public YourViewModel ViewModel { get; } = new();
}
MainWindow.xaml
<Window
x:Class="Mvvm.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"
mc:Ignorable="d">
<StackPanel>
<Button
Command="{x:Bind ViewModel.CreateNewCommand}"
Content="Create New" />
<ToggleButton
Content="Can create new"
IsChecked="{x:Bind ViewModel.CanCreateNew, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
Consider the code snippet below for a second:
<StackPanel>
<local:ChildElementWithCommand x:Name="ChildElementWithCommand">
<Button Content="Test"> // Bind this button to a command from ChildElementWithCommand
</StackPanel>
How to bind this Button to a command from the usercontrol named ChildElementWithCommand (I am pretty sure that this is not a particularly clean architecture)
ChildElementWithCommand.xaml.cs
namespace Test
{
public partial class ChildElementWithCommand : UserControl
{
public ViewModel vm;
public ICommand MyCommand;
public ChildElementWithCommand()
{
InitializeComponent();
vm = new vm();
MyCommand = vm.MyCommand; // I would like to use this command in Button above
}
}
}
This way
<StackPanel>
<local:ChildElementWithCommand x:Name="ChildElementWithCommand">
<Button Content="Test" Command="{Binding ElementName=ChildElementWithCommand, Path=SomeCommand}">
</StackPanel>
'Path=' is optional.
I gave up on MVP pattern and gave MVVM and WPF a try. I managed so far to navigate the menu showing different UserControls by clicking different buttons. DataBinding and Commands are working fine for navigation however i ran into a problem when DataBinding a textbox.
The property always get the value null and i do not know why. I added a command to get the UserID from a textbox when clicking a button and to show a MessageBox containing the UserID just to make it simple before i try anything else.
The MessageBox shows but it is empty. I added a Task.Run in the UsersModel.cs so i can see if the string updates on propertychange but value is null.
I am using a class for ObservableObject to notify OnPropertyChanged. And a RelayCommand class that inherits from ICommand. Both seems to work as intended but only when clicking the buttons to show different UserControls and Close the application. Below is sample code from View, Model and ViewModel. What am i missing?
RegisterMenu.xaml
<TextBox x:Name="RegTextUserID" Margin="163,66,148,112"
Style="{StaticResource ResourceKey=UserBox}"
Text="{Binding UserID, UpdateSourceTrigger=PropertyChanged}">
<TextBox.DataContext>
<models:UsersModel/>
</TextBox.DataContext>
</TextBox>
<Button x:Name="Button_Register" Content="Register" HorizontalAlignment="Left"
Margin="183,129,0,0" VerticalAlignment="Top"
Command="{Binding Path=GetUserCommand}">
<Button.DataContext>
<viewmodels:RegisterViewModel/>
</Button.DataContext>
</Button>
UsersModel.cs
public class UsersModel : ObservableObject
{
// backing fields
private string _userID;
// Properties
public string UserID // UserID is null
{
get { return _userID; }
set
{
SetProperty(ref _userID, value);
}
}
// testing loop for propertychange trigger
public UsersModel()
{
Task.Run(() =>
{
while (true)
{
Debug.WriteLine($"UserID: {UserID}");
Thread.Sleep(1000);
}
});
}
}
RegisterViewModel.cs
public class RegisterViewModel : ObservableObject
{
public RelayCommand GetUserCommand { get; set; }
public RegisterViewModel()
{
GetUserCommand = new RelayCommand(o =>
{
// Command is responding to button click.
});
}
}
EDIT 1
Updated the code in UsersModel.cs were i changed ObservableObject.cs to use [CallerMemberName] and SetProperty. I also removed the GetUserCommand code from RegisterViewModel.cs so i can only test the property in UsersModel.cs.
From what i understand is that the textBox binding is not working. I do not get any value even after using SetProperty method.
EDIT 2
To clarify, i used the following in XAML earlier (One DataContext) at the start. But i still had the same problem. The UsersModel property is not getting the text i type in the textbox.
<UserControl.DataContext>
<models: UserModel/>
</UserControl.DataContext>
I created a sample project with one textbox to try out setting a specific DataContext for the textbox like the code below. It is working in the sample. The output prints the text i write in the textbox. Why is it not working in the UsersModel.cs above?
MainWindow.xaml
<TextBox HorizontalAlignment="Left" Margin="249,60,0,0"
Text=" {Binding SomeText, UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<TextBox.DataContext>
<local:TestModel/>
</TextBox.DataContext>
</TextBox>
TestModel.cs (this works, i can see the loop output everytime i change the text in the textbox).
class TestModel
{
private string _someText;
public string SomeText
{
get { return _someText; }
set { _someText = value; }
}
public TestModel()
{
Task.Run(() =>
{
while (true)
{
Debug.WriteLine(SomeText);
Thread.Sleep(1000);
}
});
}
}
I was looking for the error in the wrong place. I managed to locate the error to the Style. I've tested to remove the style and the binding worked fine. I have to adjust the code of the Themes i am using. Thanks for all the comments.
I have the following DelegateCommand, created with the Prism library.
public class AddressModel : INotifyPropertyChanged
{
public ICommand MyButtonClickCommand
{
get { return new DelegateCommand<object>(FuncToCall); }
}
public void FuncToCall(object context)
{
//this is called when the button is clicked
Method1("string1", integer_number1);
}
}
I have already bonded MyButtonClickCommand to a button in XAML file.
<Button Content="Click me"
Command="{Binding MyButtonClickCommand}"/>
But I would like to use the same MyButtonClickCommand for 2 more buttons instead of creating two additional DelegateCommands MyButtonClickCommand1 & MyButtonClickCommand2.
So what I want is to add string1 and integer_number1 as parameters and call the same ICommand on different buttons like below
<Button Content="Click me"
Command="{Binding MyButtonClickCommand("string1", integer_number1)}"/>
<Button Content="Click me 2"
Command="{Binding MyButtonClickCommand("string2", integer_number2)}"/>
<Button Content="Click me 3"
Command="{Binding MyButtonClickCommand("string3", integer_number3)}"/>
You can pass an instance of any class that could be declared in XAML
public class MyCommandParameter
{
public int MyInt { get; set; }
public string MyString { get; set; }
}
to the CommandParameter property of a Button:
<Button Content="Click me" Command="{Binding ...}">
<Button.CommandParameter>
<local:MyCommandParameter MyInt="2" MyString="Hello"/>
</Button.CommandParameter>
</Button>
The MyCommandParameter instance is passed to the Execute handler method's argument:
public void FuncToCall(object parameter)
{
var param = (MyCommandParameter)parameter;
// do something with param.MyInt and param.MyString
}
Use the CommandParameter property:
<Button Content="Click me 2"
Command="{Binding MyButtonClickCommand}"
CommandParameter="2" />
You could then cast the context parameter to whatever the value of the command parameter is:
public void FuncToCall(object context)
{
string parameter = context as string;
if (int.TryParse(parameter, out int number))
{
//---
}
}
i am beginner in mvvm concept.so i tried one sample application.it contains two textbox are name and id, one submit button,one label.when i click submit button it combine the two strings from the textbox and display in label.
i can submit the values and in viewmodel the property contain the result.but its not shown in view.why..?
in view contains
<grid>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding name}" Height="23" Margin="9" Name="txtname" Width="120" />
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding id}" Height="23" Margin="9" Name="txtid" Width="120" />
<Button Command="{Binding submit}" Content="submit" Grid.Column="2" Grid.Row="2" Height="23" Name="btnsubmit" Width="75" />
<Label Content="{Binding display}" Grid.Row="3" Height="38" HorizontalAlignment="Left" Name="lbldisplay" Width="192" />
</grid>
view.cs code is
public MainWindow()
{
InitializeComponent();
DataContext = new DemoViewModel();
}
in my viewmodel contains two .cs file
one is DemoViewModelInotify.cs.in this i write the code for inotifypropertychanged.
another one is DemoViewModel.cs.this contain the property and commands.
namespace mvvmdemonixon.ViewModel
{
public class DemoViewModel :DemoViewModelInotify
{
public ICommand submit { get; set; }
public string name { get; set; }
public string display { get; set; }
public int id{get;set;}
public DemoModel model { get; set; }
public DemoViewModel()
{
submit = new DelegateCommand<object>(this.add);
}
public void add(object paramter)
{
string a= mvvmdemonixon.Model.DemoModel.addstring(name, id);
display = a;
}
}
}
my model contains
namespace mvvmdemonixon.Model
{
public class DemoModel
{
public static string addstring(string name1, int no1)
{
string display = "The Student Name Is " + name1 + "and Id Is" + no1 + ".";
return display;
}
}
}
in my app.xaml.cs
private void OnStartup(object sender, StartupEventArgs e)
{
mvvmdemonixon.MainWindow view = new MainWindow();
view.DataContext = new mvvmdemonixon.ViewModel.DemoViewModel();
view.Show();
}
in my app xaml
<Application x:Class="mvvmdemonixon.App"
Startup="OnStartup">
</Application>
advance thanks..
WPF binding engine uses INotifyPropertyChanged (for scalar properties) and INotifyCollectionChanged (for collection properties) interfaces to reflect changes, which has been made in bound data source (view model).
This:
public string display { get; set; }
means, that any property setting will not notify the view, because setter's code doesn't contain any notifications. The code should be like this:
public string display
{
get { return _display; }
set
{
if (_display != value)
{
_display = value;
OnPropertyChanged("display");
}
}
}
private string _display;
where OnPropertyChanged raises INotifyPropertyChanged.PropertyChanged event.
This is true for other view-bound properties in your view model, which should update the view.
You need to implement INotifyPropertyChanged in your ViewModel.
And raise the property changed event from each property setter.
There are two things you were missing. The first thing is your View's binding is OneTime. The second thing is you ViewModel does not notify when a property changed.
To fix the first problem, you have to update xaml like below.
<Label Content="{Binding display, Mode=OneWay}" Grid.Row="3" Height="38" HorizontalAlignment="Left" Name="lbldisplay" Width="192" />
To fix your second problem, you have to implement INotifyPropertyChanged.