I'm having a great trouble with understanding how button command works. I have something like this
{Binding TxtBox} gets value from model, let's say it's "aaa". I would like click the button and the value should appear in the second textbox (the one with {Binding TxtBox2}).
This is my xaml:
<TextBox Text="{Binding TxtBox, Source={StaticResource viewModel}}" />
<TextBox Text="{Binding TxtBox2, Source={StaticResource viewModel}}" />
<Button Command="{Binding ClickCommand}"/>
This is my ViewModel:
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
Do I really need this CommandHandler class? I copied the code from the net.
public string TxtBox
{
get { return Model.TxtBoxValue; }
set { Model.TxtBoxValue = value; }
}
public string TxtBox2 { get; set; }
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => MyAction(), _canExecute)); // I believe that when the button is clicked MyAction() is triggered, right?
}
}
private bool _canExecute = true;
public void MyAction()
{
this.TxtBox2 = this.TxtBox; // should something like this work? Because right now it doesn't
}
The second textbox's binding never gets notified that it's bound property is changed. When you set this.TxtBox2 you should fire the propertychanged event for that property so the binding will be updated.
See think link for everything on bindings
I don't know if you are using prism as mvvm framework but that comes with the DelegateCommand class. I don't think there is a simple/lightweight implementation in the .net framework. See this link for the mvvm framework and the delegate command
The View reacts to binding changes through PropertyChanged events, of which you have none. Have anything that binds to the View implement INotifyPropertyChanged and then fire events when props change, and you're all set for your bindings to work (one way or two way).
Change your model to look like this and it should work for you.
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged = null;
protected virtual void RaisePropertyChanged(string propName)
{
if(PropertyChanged != null)
{
Task.Run(() => PropertyChanged(this, new PropertyChangedEventArgs(propName)));
}
}
#endregion
public string TxtBox
{
get { return Model.TxtBoxValue; }
set
{
Model.TxtBoxValue = value;
RaisePropertyChanged("TxtBox");
}
}
// presuming TxtBox2Value is in Model...else use a field
public string TxtBox2
{
get { return Model.TxtBox2Value; }
set
{
Model.TxtBox2Value = value;
RaisePropertyChanged("TxtBox2");
}
}
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => MyAction(), _canExecute)); // I believe that when the button is clicked MyAction() is triggered, right?
}
}
private bool _canExecute = true;
public void MyAction()
{
this.TxtBox2 = this.TxtBox; // should something like this work? Because right now it doesn't
}
}
IMO - it is better to have your Model implement INotifyPropertyChanged and then bind directly to it rather than wrap it in your ViewModel. If Model : INotifyPropertyChanged, then your ViewModel now looks like this:
public class MyViewModel
{
// fire prop changed event here if this model will be swapped out after the ctor...otherwise don't worry about it
public Model Model { get; set; }
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => MyAction(), _canExecute));
}
}
private bool _canExecute = true;
public void MyAction()
{
Model = new Model();
Model.TxtBox2 = "Some new value";
}
}
...and your xaml changes to this:
<TextBox Text="{Binding Model.TxtBox, Source={StaticResource viewModel}}" />
<TextBox Text="{Binding Model.TxtBox2, Source={StaticResource viewModel}}" />
<Button Command="{Binding ClickCommand}"/>
Related
I have the following textbox
<TextBox Grid.Column="1"
Grid.Row="1"
Name="groupAddressBox"
Width ="80"
Text="{Binding Path=GroupAddress, Converter={StaticResource groupAddressConverter}}"/>
When I change the text manually, it's all good.
But when I try to do this via a button
private void Test_Click(object sender, RoutedEventArgs e)
{
groupAddressBox.Text = "0/0/1";
}
Although the text changes, the source is not updated, and when I click on ok, it recognizes the value that was there before the change.
I cannot upgrade the source straight away, so I prefer to do this this way.
Is there something that can help me force the source upgrade via this way?
Based on your question, I tried to create a Simple Example of MVVM Pattern with very basic functionality. Please do necessary change to XAML and CS file as I took the highlighted code only.
Helper Classes
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class ViewModel : ViewModelBase
{
private string groupAddress;
public string GroupAddress
{
get
{
return groupAddress;
}
set
{
if(value != groupAddress)
{
groupAddress = value;
OnPropertyChanged("GroupAddress");
}
}
}
public ViewModel()
{
}
private ICommand clickCommand;
public ICommand ClickCommand
{
get
{
return clickCommand ?? (clickCommand = new CommandHandler(() => MyAction(), true));
}
}
public void MyAction()
{
GroupAddress = "New Group Address";
}
}
Window Xaml
<TextBox Grid.Column="1" Grid.Row="1" Width ="80"
Text="{Binding GroupAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Push" Style="{StaticResource TransparentButtonStyle}"
Margin="5" Command="{Binding ClickCommand}"/>
Window Xaml cs
ViewModel vm = new ViewModel();
this.DataContext = vm;
I have a StackLayout property in xmal like shown below:
<StackLayout x:Name="_infoView"
Margin="0,10,0,10"
BackgroundColor="Black"
IsVisible="{Binding State}"/>
and a binding bool variable in ViewModel
private Boolean _state = true;
public Boolean State
{
get { return _state; }
set { }
}
I have a button in my xmal and would like to control the visibility of my StackLayout, So I did something like this:
<Button x:Name="CloseButton"
Grid.Row="0"
Grid.Column="3"
Command="{Binding CloseWindowCommand}"/>
and in ViewModel
CloseWindowCommand = new Command(CloseWindowTapped, CanCloseWindowTapped);
public ICommand CloseWindowCommand { get; set; }
public void CloseWindowTapped()
{
State = false;
}
public bool CanCloseWindowTapped()
{
return true;
}
I'd assume, by tap on the CloseButton, my StackLayout will gone... but it is not working
ViewModel should implement INotifyPropertyChanged interface for informing View about changes.
public class ViewModel : INotifyPropertyChanged
{
// Implementing INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
}
// In the setter of property raise event to inform view about changes
private Boolean _state = true;
public Boolean State
{
get
{
return _state;
}
set
{
_state = value;
RaisePropertyChanged();
}
}
}
I am trying to bind an observable collection of strings. But when i launch an app, I receive Exception that Items collection must be empty before using ItemsSource. I have no elements in collection when it is binding, so what can be the issue?
My Xaml
<ListBox ItemsSource="{Binding Users}" Margin="10,77,805,228" Grid.RowSpan="2">
<ListBoxItem>
<DataTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</DataTemplate>
</ListBoxItem>
</ListBox>
<Button x:Name="AddUserButton" Content="Додати" Command="{Binding AddUserCommand}" RenderTransformOrigin="0.512,1.9" />
My ViewModel (command and observablecollection)
public class UsersTabViewModel : ViewModelBase
{
private ObservableCollection<string> users;
private string text;
private ICommand addUserCommand;
private bool _canExecute;
public UsersTabViewModel()
{
_canExecute = true;
Users = new ObservableCollection<string>();
}
public ObservableCollection<string> Users { get; set; }
public ICommand AddUserCommand
{
get
{
return addUserCommand ?? (addUserCommand = new CommandHandler(() => AddUserAction(), _canExecute));
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
}
}
//text is bound to here
private void AddUserAction()
{
Users.Add("collection");
}
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
As the error is trying to tell you, you can't have any items if you use ItemsSource to bind them instead.
Remove your <ListBoxItem>.
To set a template for the bound items, set <ListBox.ItemTemplate>.
I fixed it clearing my ListBox with Items.Clear()
Hi i want to bind button with other listView.Item. What i want is to have something like we have on stackoverflow.
But i have problem with having increasing/decreasing value. I have event Click but i dont knew how to get corresponding item on list and increase/decrease value.
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label Width="706" Height="75" Content="{Binding feedback}"/>
<StackPanel Orientation="Vertical">
<Button Name="buttonUp" Content="^" Command="{Binding upVoteCommand}" />
<Label HorizontalContentAlignment="Center" Width="50" Content="{Binding grade}"/>
<Button Name="buttonDown" Content="v" Command="{Binding upVoteCommand}"/>
</StackPanel>
</StackPanel>
<Label>-</Label>
</StackPanel >
EDIT
class A {
public string feedback {
get;
set;
}
public int grade {
get;
set;
}
private ICommand _upVoteCommand;
private ICommand _downVoteCommand;
public ICommand upVoteCommand {
get {
return _upVoteCommand;
}
set {
_upVoteCommand = value;
}
}
public ICommand downVoteCommand {
get {
return _downVoteCommand;
}
set {
_downVoteCommand = value;
}
}
}
EDIT I used this button.Commmand but still it not working. I dont knew what to do with this commands.
First you'll need your implementation of ICommand so you can bind commands from view model to controls, something like this:
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute) : this(execute, null) { }
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter) { _execute(parameter); }
}
then in you class, where you publish Feedback, you'll need to publish 2 new RelayCommand for up/down vote that will modify Feedback property accordingly. Below you can find my class that I used for tests:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private int _feedback = 0;
public int Feedback
{
get { return _feedback; }
set
{
if (_feedback == value) return;
_feedback = value;
OnPropertyChanged("Feedback");
}
}
private RelayCommand _upVoteCmd;
public ICommand UpVoteCmd
{
get
{
if (_upVoteCmd == null) _upVoteCmd = new RelayCommand(o => Feedback += 1);
return _upVoteCmd;
}
}
private RelayCommand _downVoteCmd;
public ICommand DownVoteCmd
{
get
{
if (_downVoteCmd == null) _downVoteCmd = new RelayCommand(o => Feedback -= 1);
return _downVoteCmd;
}
}
}
and then you bind your new commands in XAML like this:
<Button Content="+" Command="{Binding Path=UpVoteCmd}"/>
<TextBlock Text="{Binding Path=Feedback}"/>
<Button Content="-" Command="{Binding Path=DownVoteCmd}"/>
RoutedEvents don't work so easily with DataTemplates, because you don't have a code behind where your event code could be placed. While there are ways to do that, you can just use Commands to do the same. In the view model for each item (i just assume you use MVVM) create properties called UpVoteCommand and DownVoteCommand of type ICommand, DelegateCommands are quiet handy for this. Bind them to the Command property and remove the Click handler in your DataTemplate.
[EDIT]
Small example of a possible Viewmodel for one entry in the list, which can be up or downvoted.
class MyEntryViewModel : INotifyPropertyChanged
{
public MyEntryViewModel()
{
UpVoteCommand = new DelegateCommand(OnUpVoteCommand);
}
public int Votes
{
get {return mVotes;}
set {mVotes = value; RaiseProperty("Votes");}
}
public ICommand UpVoteCommand
{
get; private set;
}
void OnUpVoteCommand(object aParameter)
{
Votes++;
}
}
i left the implementation of INotifyPropertyChanged and the down vote command for sake of simplicity.
I recently started using WPF and the MVVM framework, one thing that I have wanted to do is to have a type safe implementation of ICommand so I do not have to cast all the command paramaters.
Does anyone know of a way to do this?
Not using that syntax, as you probably found:
error CS0701: ``System.Func`' is not a valid constraint. A constraint must be an interface, a non-sealed class or a type parameter
Your best bet is to encapsulate the Func<E,bool> semantics in an interface, like:
interface IFunctor<E>
{
bool Execute(E value);
}
and then use this interface in the class definition. Although, I am wondering what you're looking to accomplish as there may be another approach to your problem.
Per the comment that #Alex is looking for a strongly typed ICommand implementation:
public FuncCommand<TParameter> : Command
{
private Predicate<TParameter> canExecute;
private Action<TParameter> execute;
public FuncCommand(Predicate<TParameter> canExecute, Action<TParameter> execute)
{
this.canExecute = canExecute;
this.execute = execute;
}
public override bool CanExecute(object parameter)
{
if (this.canExecute == null) return true;
return this.canExecute((TParameter)parameter);
}
public override void Execute(object parameter)
{
this.execute((TParameter)parameter);
}
}
Used like so:
public class OtherViewModel : ViewModelBase
{
public string Name { get; set; }
public OtherViewModel(string name) { this.Name = name; }
}
public class MyViewModel : ViewModelBase
{
public ObservableCollection<OtherViewModel> Items { get; private set; }
public ICommand AddCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
public MyViewModel()
{
this.Items = new ObservableCollection<OtherViewModel>();
this.AddCommand = new FuncCommand<string>(
(name) => !String.IsNullOrEmpty(name),
(name) => this.Items.Add(new OtherViewModel(name)));
this.RemoveCommand = new FuncCommand<OtherViewModel>(
(vm) => vm != null,
(vm) => this.Items.Remove(vm));
}
}
XAML:
<ListBox x:Name="Items" ItemsSource="{Binding Items}" />
<Button Content="Remove"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding SelectedItem, ElementName=Items}" />
<StackPanel Orientation="Horizontal">
<TextBox x:Name="NewName" />
<Button Content="Add"
Command="{Binding AddCommand}"
CommandParameter="{Binding Text, ElementName=NewName}" />
</StackPanel>
I would recommend using Microsoft's DelegateCommand or RelayCommand, or any other implementation of either of these.
Your command class should subscribe to ICommand and define the CanExecuteChanged as below.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}