How to manually increment/decrement a WPF progress bar using MVVM - c#

I have searched in Google using a progress bar in WPF that will be manually incremented and decremented using a plus and minus button. But, to no avail haven't found one that is similar to what I want though.
How do I implement a WPF progress bar that will be manually incremented or decremented (using buttons) in an MVVM way. The screen capture below shows the mock-up UI design.
The image show that when the user click the plus button, the progress bar will be incremented by 10 minutes. On the other hand, the minus button when clicked, decrements the progress bar by 10 minutes.
I'm just starting to learn WPF and MVVM. Any help is greatly appreciated.

I created a simple example which uses WPF and MVVM to show how one model can be displayed with different views. Here in xaml I placed on a form Slider and ProgressBar - they are Views for our ViewModel. The properties we need (Minimum, Maximum, Value) are binded to the ViewModel's properties. "Plus" and "Minus" buttons' properties "Command" are also binded to the corresponding props in the ViewModel(IncreaseCommand, DecreaseCommand).
<Window>
<StackPanel Orientation="Horizontal">
<Button Width="50" Height="40" Content="-" Command="{Binding DecreaseCommand}"/>
<StackPanel Width="400" Orientation="Vertical">
<Slider Height="40" Margin="0,50,0,0" Minimum="{Binding Minimum}" Maximum="{Binding Maximum}" Value="{Binding Value}"/>
<ProgressBar Height="40" Margin="0,100,0,0" Minimum="{Binding Minimum}" Maximum="{Binding Maximum}" Value="{Binding Value}"/>
<TextBlock TextAlignment="Center" Margin="0,50,0,0" Text="{Binding Value}"/>
</StackPanel>
<Button Width="50" Height="40" Content="+" Command="{Binding IncreaseCommand}"/>
</StackPanel>
</Window>
For implementing the commands functionality in ViewModel you will need to create an implementation of ICommand interface:
public class RelayCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
And here's the ViewModel class, it implements INotifyPropertyChanged interface to keep views updated.
public class ViewModel:INotifyPropertyChanged
{
public ViewModel()
{
_value = 0;
_minimum = 0;
_maximum = 180;
_step = 10;
}
private int _step;
private int _minimum;
private int _maximum;
private ICommand _increaseCommand;
public ICommand IncreaseCommand
{
get
{
if (_increaseCommand == null)
{
_increaseCommand = new RelayCommand(
p => _value + _step <= _maximum,
Increase);
}
return _increaseCommand;
}
}
private ICommand _decreaseCommand;
public ICommand DecreaseCommand
{
get
{
if (_decreaseCommand == null)
{
_decreaseCommand = new RelayCommand(
p => _value - _step >= _minimum,
Decrease);
}
return _decreaseCommand;
}
}
private void Increase(object param)
{
Value = Value + _step;
}
private void Decrease(object param)
{
Value = Value - _step;
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; InvokePropertyChanged(new PropertyChangedEventArgs("Value")); }
}
public int Minimum
{
get { return _minimum; }
}
public int Maximum
{
get { return _maximum; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
And the last thing to get it all working is to create new ViewModel and set DataContext of a window to this model:
public MainWindow()
{
InitializeComponent();
var model = new ViewModel();
DataContext = model;
}

I think you should solve that by use custom Slider control of WPF instead Progress bar.
This link can help you : http://www.codescratcher.com/wpf/custom-slider-control-in-wpf/

Related

WPF MVVM - Simple Binding Button with Textbox (Icommand)

What I'm trying to do is: when the textbox contains the value "123" it should enable the button and allow me to click it.
Solution Image &
View Image
I can not find a way to trigger the Button Command (Class called SpecialCommand.cs) based on my Button parameters. Could you support where I'm getting this MVVM pattern wrong?
WPF View [MainWindow.xaml]:
<Window.Resources>
<ViewModel:MainWindowVM x:Key="WindowVm"></ViewModel:MainWindowVM>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="123"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding SpecialCommand, Source={StaticResource WindowVm}}" CommandParameter="{Binding Text, ElementName=textBox, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</Grid>
ViewModel [MainWindowVM.cs]:
public class MainWindowVM
{
private SpecialCommand _specialCommand;
public SpecialCommand SpecialCommand { get => _specialCommand; set => _specialCommand = value; }
public MainWindowVM()
{
_specialCommand = new SpecialCommand();
}
}
Command [SpecialCommand.cs]
public class SpecialCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null && (parameter as string) == "123")
return true;
return false;
}
public void Execute(object parameter)
{
MessageBox.Show("Button Pressed!");
}
public event EventHandler CanExecuteChanged;
}
What I believe, maybe this is what I'm getting wrong is since Button & Textbox are in the View I don't need add/modify any method in my SpecialCommand implementation. They should be able to see when a property is changed.
Like the CanExecuteChanged() below, this command raises a lot of times and seems overkill for this small task.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
There is no need to make it more complicated than it needs to be.
public class MainWindowVM
{
private string m_OneTwoThree;
public string OneTwoThree{
get { return OneTwoThree; }
set {
if (m_OneTwoThree != value){
m_OneTwoThree = value;
NotifyPropertyChanged(nameof(OneTwoThree)); //if you need this let me know
}
}
}
public MainWindowVM()
{
}
public ICommand RandomCommand { get { return new RelayCommand(OnRandom, IsOneTwoThree); } }
private void OnRandom()
{
//do stuff
}
private bool IsOneTwoThree(){
if (OneTwoThree == "123"){
return true;
}
return false;
}
}
You'll also have to update your xaml I don't think it'll be able to find 'OneTwoThree' so you'll have to bind it yourself, however you usually would.
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="{Binding OneTwoThree}"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding RandomCommand, Source={StaticResource WindowVm}}"/>
</StackPanel>
If you have any questions just ask.
This is my RelayCommand : Use RelayCommand("thing to execute", "if function returns true you can execute");
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null) {
return true;
}
else {
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
You need a way to tell the command that it should check whether it CanExecute. This is done by triggering the event CanExecuteChanged. It tells the button to requery the CanExecute property.
For this to work, I would add a text property to your view model and bind the textbox to it.
In SpecialCommand add the method:
public void TriggerCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In the class MainWindowVM add the property:
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
_specialCommand.TriggerCanExecuteChanged();
}
}
}
View Model to implementing INotifyPropertyChanged (see comments):
public class MainWindowVM : INotifyPropertyChanged
{
public SpecialCommand SpecialCommand { get; set; } = new SpecialCommand();
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
OnPropertyChanged(nameof(Text));
SpecialCommand.TriggerCanExecuteChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

How to bind with Mode=OneWay and only propagate changes back on save?

Question
How can I make it so that changes to a note are only propagated back to the list, when the Save button is clicked instead on "lost focus"?
And the Save button should only be enabled when the note has been changed.
UI
The example application looks like this:
The current behaviour is:
Clicking on a note puts its text into the TextBox; that's fine.
The changed text from the TextBox gets written back to the list when the TextBox loses the focus (default binding behaviour); but I only want that to happend when the Save button is clicked.
The Save button is always activated because the CanExecute(object parameter) isn't correctly implemented yet; it should only get activated when the TextBox text is different from the selected note's text.
My research so far
Option 1: Some Internet sources say to bind a different property to the TextBox and to programmatically check whether it is different from the SelectedItem of the ListView. I would have hoped that there was a way without introducing a third property in addition to the already existing ListOfNotes and SelectedNote.
Option 2: Some Internet sources recommend to configure Mode=OneWay so that clicking an item in the ListView updates the TextBox, but not the other way around. This sounds like the solution I would prefer, but I wasn't able to figure out from the code examples how to raise an event programmatically so that the change in the TextBox gets written back to the ListView when the Save button is clicked.
I've found other Stackoverflow questions that seem to be similar to mine, but the answers to those haven't helped me fix the problem:
WPF databinding after Save button click
Code
This example currently does two-way binding on focus lost. How do I need to change it to get the above described behaviour?
https://github.com/lernkurve/WpfBindingOneWayWithSaveButton
MainWindow.xaml
<Window x:Class="WpfBindingOneWayWithSaveButton.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:wpfBindingOneWayWithSaveButton="clr-namespace:WpfBindingOneWayWithSaveButton"
mc:Ignorable="d"
Title="MainWindow" Height="188.636" Width="299.242">
<Window.DataContext>
<wpfBindingOneWayWithSaveButton:MainWindowsViewModel />
</Window.DataContext>
<Grid>
<GroupBox Header="List of notes" HorizontalAlignment="Left" VerticalAlignment="Top" Height="112" Width="129" Margin="0,24,0,0">
<ListView ItemsSource="{Binding ListOfNotes}" SelectedItem="{Binding SelectedNote}" DisplayMemberPath="Text" HorizontalAlignment="Left" Height="79" VerticalAlignment="Top" Width="119" Margin="0,10,-2,0"/>
</GroupBox>
<GroupBox Header="Change selected note" HorizontalAlignment="Left" Margin="134,24,0,0" VerticalAlignment="Top" Height="112" Width="151">
<Grid HorizontalAlignment="Left" Height="89" Margin="0,0,-2,0" VerticalAlignment="Top" Width="141">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="101*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding SelectedNote.Text}" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="121" Margin="10,7,0,0" Grid.ColumnSpan="2"/>
<Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="121" Margin="10,35,0,0" Grid.ColumnSpan="2"/>
</Grid>
</GroupBox>
</Grid>
</Window>
MainWindowsViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class MainWindowsViewModel
{
public ObservableCollection<Note> ListOfNotes { get; set; }
public Note SelectedNote { get; set; }
public ICommand SaveCommand { get; set; }
public MainWindowsViewModel()
{
ListOfNotes = new ObservableCollection<Note>
{
new Note { Text = "Note 1" },
new Note { Text = "Note 2" }
};
SaveCommand = new SaveCommand(this);
}
}
}
SaveCommand.cs
using System;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
// What should go here?
return true;
// Pseudo code
// return (is the TextBox text different from the original note text)
}
public void Execute(object parameter)
{
// What should go here?
// Pseudo code
// Let WPF know that the TextBox text has changed
// Invoke the binding so it propagates the TextBox text back to the list
}
public event EventHandler CanExecuteChanged;
}
}
Note.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingOneWayWithSaveButton
{
public class Note : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Bind the text to the CommandParameter of the SaveButton so it gets passed to the Save method for updating.
<TextBox x:Name="NoteTextBox" Text="{Binding SelectedNote.Text, Mode=OneTime}" ../>
<Button Command="{Binding SaveCommand}"
CommandParameter="{Binding ElementName=NoteTextBox, Path=Text}",
Content="Save" />
and
public bool CanExecute(object parameter)
{
return vm.SelectedNote.Text != parameter as string;
}
public void Execute(object parameter)
{
vm.SelectedNote.Text = parameter as string;
}
Option one is the easiest to implement, you will need to clone the Note object and set it to a separate property.
in your xaml, change your list view to the following so it now binds the SelectedIndex instead of the SelectedItem.
<ListView ItemsSource="{Binding ListOfNotes}" SelectedIndex="{Binding SelectedIndex}" DisplayMemberPath="Text" ...
And change TextBox to the following so it updates the binding as you type
<TextBox Text="{Binding Path=SelectedNote.Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" ...
In Note.cs we add the Clone() method.
public class Note : INotifyPropertyChanged
{
public Note Clone()
{
return new Note()
{
Text = this.Text
};
}
//... The rest stays the same
}
In MainWindowsViewModel.cs we add new properties for the SelectedIndex and clone the object when we detect a index has changed. We also need to add INotifyPropertyChanged so we can update the SelectedNote from the codebehind when we do the Clone()
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
_selectedNote = value;
OnPropertyChanged();
}
}
//... The rest stays the same
}
In SaveCommand.cs we add the logic for CanExecute and add the subscriptions to CommandManager.RequerySuggested, this automatically makes it requery the CanExecute any time any binding changes. This can be a little ineffecent, if you wanted to you could expose a RaiseCanExecuteChanged() publicly but it would be MainWindowsViewModel responsibility to call it any time vm.SelectedIndex or vm.SelectedNote.Text changed.
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
UPDATE: Here is a updated version that does not use CommandManager
MainWindowsViewModel.cs
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
RecheckSaveCommand();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
if (_selectedNote != null)
{
PropertyChangedEventManager.RemoveHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
_selectedNote = value;
if (_selectedNote != null)
{
PropertyChangedEventManager.AddHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
OnPropertyChanged();
}
}
private void SelectedNoteTextChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
RecheckSaveCommand();
}
private void RecheckSaveCommand()
{
var command = this.SaveCommand as WpfBindingOneWayWithSaveButton.SaveCommand; //"this." and "WpfBindingOneWayWithSaveButton." are not necessary but I wanted to be explicit.
if (command != null)
{
command.RaiseCanExecuteChanged();
}
}
//...
}
SaveCommand.cs
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
You should not use OneWay but rather an UpdateSourceTrigger of value Explicit. BindingGroups can do this for you though, here's a simple example:
<!-- For change observation -->
<TextBlock Text="{Binding Text}"></TextBlock>
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup x:Name="EditGroup"></BindingGroup>
</StackPanel.BindingGroup>
<TextBox Text="{Binding Text}"></TextBox>
<Button>
<Button.Command>
<local:CommitGroupCommand BindingGroup="{x:Reference EditGroup}"/>
</Button.Command>
Save
</Button>
</StackPanel>
public class CommitGroupCommand : ICommand
{
public BindingGroup BindingGroup { get; set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
BindingGroup.UpdateSources();
}
}
(You could add a validation rule to your binding that requires the value to be different and use that for the CanExecute implementation.)
Using this method allows you to bind directly to the object you intend to edit, so you don't need to copy around values first.

ViewModel property not getting updated on changing TextBox text

I have a TextBox in a UserControl which is binded to a property in the MainWindow's ViewModel.
Now when I type something in the Textbox, it updates the property in the viewmodel but if I change Textbox's text in code behind, the viewmodel property isn't updating.
Actually the textbox is getting the value from FileDialog which is opened when I click the button, so Textbox is getting its text from code behind.
UserControl XAML:
<StackPanel Orientation="Horizontal" Grid.Row="1" HorizontalAlignment="Left">
<TextBox x:Name="TextBoxFileOrFolder" Text="{Binding FolderOrFileName}" Grid.Row="1" Width="200" Height="100" HorizontalAlignment="Left"></TextBox>
<Button x:Name="ButtonRun" Content="Run" Click="ButtonRun_OnClick" Width="200" Height="100" Margin="10"></Button>
</StackPanel>
UserControl code behind
private void ButtonRun_OnClick(object sender, RoutedEventArgs e)
{
TextBoxFileOrFolder.Text = "FileName" + new Random().Next();
}
ViewModel:
public class MainViewModel: INotifyPropertyChanged
{
public MainViewModel()
{ }
private string folderOrFileName;
public string FolderOrFileName
{
get { return folderOrFileName; }
set
{
if (folderOrFileName!=value)
{
folderOrFileName = value;
RaisePropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
# endregion
}
but if I change Textbox's text in code behind, the viewmodel property isn't updating.
That’s because if you set the Text property of the text box in the code-behind, you are overwriting the binding. So while you update the view, the link to your view model is gone, so there is nothing that will update it. And also, when the view model update the values, the view also will not be updated.
To solve this, simply don’t set properties that have a binding in the code-behind.
Instead of handling the button event in the code-behind and updating the view, you should have your button command bind to your view model and update the FolderOrFileName in the view model.
If you binding to Text property you should set property in the ViewModel to change value of TextBox:
public partial class MainWindow : Window
{
private MainViewModel _vm;
public MainWindow()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
private void ButtonRun_OnClick(object sender, RoutedEventArgs e)
{
_vm.FolderOrFileName = "FileName" + new Random().Next();
}
}
In your case you should use command to modify data.
1) You should create class which inherit from ICommand:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
2) Next you should create command in ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
ChangeFileName = new DelegateCommand(OnChangeFileName);
}
public ICommand ChangeFileName { get; private set; }
private void OnChangeFileName(object param)
{
FolderOrFileName = "FileName" + new Random().Next();
}
private string folderOrFileName;
...
3) And finally you should add binding to Button.Command property in View:
<Button x:Name="ButtonRun" Content="Run" Command="{Binding ChangeFileName}" Width="200" Height="100" Margin="10"></Button>
Make sure your binding is set up as "TwoWay" - UI -> VM and VM -> UI
<TextBox x:Name="TextBoxFileOrFolder" Text="{Binding FolderOrFileName, Mode=TwoWay}" Grid.Row="1" Width="200" Height="100" HorizontalAlignment="Left"></TextBox>

WPF and pattern MVVM how to get value from textbox, send it to ViewModel and save dto model

i'm new in WPF and MVVM. I read many articles about WPF commands, but i have still problem with sending value from property text of textbox to ViewModel.
I'm using entity framework code first.
I want to show text from textbox in MessageBox, but when I click to button with command, linked property of viewmodel is null.
Please can you help me?
View- DetailIncidentWindow.xaml
xmlns:wm="clr-namespace:Admin.ViewModels"
<StackPanel>
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
<TextBlock Text="Text komentáře:" Style="{StaticResource TextBlockLabel}" Margin="0,10,0,0"/>
<TextBox Name="TextBox_textKomentar" Width="auto" Height="100" TextWrapping="Wrap" Text="{Binding TextKomentar, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding TextKomentar, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
Ribbon button- DetailIncidentWindow.xaml
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
<Custom:RibbonButton
LargeImageSource="..\Shared\img\save_diskete.png"
Label="Show text"
Command="{Binding ButtonCommand}">
</Custom:RibbonButton>
ViewModel- KomentarViewModel.cs
namespace Admin.ViewModels
{
class CommentViewModel:BaseViewModel
{
#region Data
private string textKomentar;
public string TextKomentar
{
get
{
return textKomentar;
}
set
{
textKomentar = value;
OnPropertyChanged("TextKomentar");
}
}
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
OnPropertyChanged("ButtonCommand");
}
}
#endregion
#region Constructor
public CommentViewModel()
{
ButtonCommand = new RelayCommand(new Action<object>(ShowMessage));
}
#endregion
#region Methods
public void ShowMessage(object obj)
{
MessageBox.Show(TextKomentar);
}
#endregion
}
}
Command- RelayCommand.cs
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello World");
}
}
#endregion
}
You should not create multiple instances of your view model, like you do in
<StackPanel.DataContext>
<wm:CommentViewModel/>
</StackPanel.DataContext>
and
<Custom:RibbonGroup.DataContext>
<wm:CommentViewModel/>
</Custom:RibbonGroup.DataContext>
The value of the DataContext property is inherited by child elements, so you could just set it at the top level, e.g. the Window:
<Window ...>
<Window.DataContext>
<wm:CommentViewModel/>
</Window.DataContext>
...
</Window>

How can I handle multiple CheckBoxes in the MVVM pattern?

Binding checkbox in WPF is common issue, but I am still not finding example code which is easy to follow for beginners. I have check box list in WPF to select favorite sports’ name. The number of checkboxes is static in my case. Can anyone show me how to implement ViewModel for this issue?
FavoriteSportsView.xaml:
<StackPanel Height="50" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="150">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Hockey"
Content="Hockey"
Margin="5" />
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Golf"
Content="Golf"
Margin="5" />
</StackPanel>
FavoriteSportsViewModel.cs
public class FavoriteSportsViewModel.cs {
//Since I am using the same IsChecked in all check box options, I found all check
//boxes gets either checked or unchecked when I just check or uncheck one option.
//How do i resolve this issue? I don't think i need seprate IsChecked for each
//check box option.
private bool _isChecked;
public bool IsChecked{
get {
return _isChecked;
}
set { if (value != _isChecked)
_isChecked = value;
this.OnPropertyChanged("IsChecked");
}
}
//How do i detect parameter in this method?
private ICommand _sportsResponseCommand;
public ICommand SportsResponseCommand
{
get
{
if (_sportsResponseCommand== null)
_sportsResponseCommand= new
RelayCommand(a => DoCollectSelectedGames(), p => true);
return _sportsResponseCommand;
}
set
{
_sportsResponseCommand= value;
}
}
private void DoCollectSelectedGames(){
//Here i push all selected games in an array
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm not sure how to do the following in above ViewModel:
1. How do I implement single method to handle all my options?
2. how do I detect each one of the checkboxes to see whether checked or not
3. How do i utlize CommandParameter?
4. How do i implement SportsResponseCommand correctly
Your view model should look something like this:
public class MyViewModel : INotifyPropertyChanged
{
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//bindable property
private bool _football;
public bool Football
{
get { return _football; }
set
{
if (value != _football)
{
_football = value;
this.OnPropertyChanged("Football");
}
}
}
//... and the same for Golf and Hockey
}
Then you associate your view model with the view by setting the DataContext property (this will most likely be in the Window or UserControl code behind, though there are a lot of ways to achieve this).
Finally, update your bindings so that they look like:
<CheckBox IsChecked="{Binding Football, Mode=TwoWay}"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"
Content="Football"
Margin="5" />
As a final comment, you shouldn't really need to bind the Command property - you can just write whatever code you need to run in the property setter on the view model.
I highly recommend you to read this http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
I describe a solution below I tried to not modify your XAML code but it is not the only way (or the best approach) but contains all necessary elements!
At first step you need your model I call it Model_Sport
public class Model_Sport : INotifyPropertyChanged
{
#region Constructor
public Model_Sport(string name, ICommand command)
{
Name = name;
SportsResponseCommand = command;
}
#endregion
static readonly PropertyChangedEventArgs _NameEventArgs = new PropertyChangedEventArgs("Name");
private string _Name = null;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged(_NameEventArgs);
}
}
static readonly PropertyChangedEventArgs _SportsResponseCommandEventArgs = new PropertyChangedEventArgs("SportsResponseCommand");
private ICommand _SportsResponseCommand = null;
public ICommand SportsResponseCommand
{
get { return _SportsResponseCommand; }
set
{
_SportsResponseCommand = value;
OnPropertyChanged(_SportsResponseCommandEventArgs);
}
}
static readonly PropertyChangedEventArgs _IsCheckedEventArgs = new PropertyChangedEventArgs("IsChecked");
private bool _IsChecked = false;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
OnPropertyChanged(_IsCheckedEventArgs);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
if (PropertyChanged != null)
{
PropertyChanged(this, eventArgs);
}
}
#endregion
}
Now you need a way to delegate your command “SportsResponseCommand”, DelegateCommand object will help you to do that
public class DelegateCommand : ICommand
{
private readonly Action<object> _ExecuteMethod;
private readonly Func< object, bool> _CanExecuteMethod;
#region Constructors
public DelegateCommand(Action<object>executeMethod, Func<object, bool> canExecuteMethod)
{
if (null == executeMethod)
{
throw new ArgumentNullException("executeMethod", "Delegate Command Delegates Cannot Be Null");
}
_ExecuteMethod = executeMethod;
_CanExecuteMethod = canExecuteMethod;
}
public DelegateCommand(Action<object>executeMethod) : this(executeMethod, null) { }
#endregion
#region Methods
public bool CanExecute(object parameter)
{
if (_CanExecuteMethod == null) return true;
return _CanExecuteMethod(parameter);
}
public void Execute(object parameter)
{
if (_ExecuteMethod == null) return;
_ExecuteMethod(parameter);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object parameter)
{
Execute(parameter);
}
#endregion
}
Now “ViewModel”
public class ViewModel
{
#region property
public Dictionary<string, Model_Sport> Sports { get; set; }
public DelegateCommand SportsResponseCommand { get; set; }
#endregion
public ViewModel()
{
Sports = new Dictionary<string, Model_Sport>();
SportsResponseCommand = new DelegateCommand(p => execute_SportsResponseCommand(p));
buildSports();
}
private void buildSports()
{
Model_Sport football = new Model_Sport("Football", SportsResponseCommand);
Model_Sport golf = new Model_Sport("Golf", SportsResponseCommand);
Model_Sport hockey = new Model_Sport("Hockey", SportsResponseCommand);
football.IsChecked = true; // just for test
Sports.Add(football.Name, football);
Sports.Add(golf.Name, golf);
Sports.Add(hockey.Name, hockey);
}
private void execute_SportsResponseCommand(object p)
{
// TODO :what ever you want
MessageBox.Show(p.ToString());
}
}
Now View
Remember to set datacontext for your Window
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
Then in XAML
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" >
<CheckBox DataContext="{Binding Path=Sports[Football]}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox DataContext="{Binding Path=Sports[Hockey]}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Hockey"
Content="Hockey"
Margin="5" />
<CheckBox DataContext="{Binding Path=Sports[Golf]}" IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Golf"
Content="Golf"
Margin="5" />
</StackPanel>
If you just want a property in your ViewModel to get updated when the IsChecked changes, replace the Binding for IsChecked to a boolean property in your ViewModel that raises NotifyPropertyChanged on its "set".
Now if you want to perform an action everytime IsChecked changes for one of the 3 CheckBoxes:
First of all, replace your CommandParameter with "{Binding RelativeSource={RelativeSource Mode=Self}}"
In your ViewModel (that should implement INotifyPropertyChanged), create an ICommand (SportsResponseCommand) that takes a CheckBox in parameter.
In the command's method, check for the Content of your CheckBox, and for the "IsChecked" property then do your stuff with them.
If you have further questions let me know.
You can assign a view model by using this
//for the view
partial class MainView:Window
{
InitializeComponent();
this.DataContext=new MainViewModel();
}
//ViewModel Code
public class MainViewModel: INotifyPropertyChanged
{
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//bindable property
private bool _football;
public bool Football
{
get { return _football; }
set
{
if (value != _football)
{
_football = value;
this.OnPropertyChanged("Football");
}
}
}
//... and the same for Golf and Hockey
}`
and then you can implement Binding in XAML as
<CheckBox IsChecked="{Binding Football, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />

Categories

Resources