I have a view that contains a list box with various items in button form and a checkbox for each of the button. XAML:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox x:Name="CheckFavorite"
Width="Auto"
Height="Auto"
AutomationProperties.AutomationId="AID_FavoritesCheck"
IsChecked="{Binding Path=IsChecked,
Mode=TwoWay}"
Visibility="{Binding IsFavoriteConfiguredAndInDA,
Converter={StaticResource boolToVisibility}}"
Checked="OnContentChanged"
Unchecked="OnContentChanged"/>
<Button Grid.Column="1"
Width="240"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
AutomationProperties.AutomationId="AID_BtnLaunchFavorite"
Command="{Binding Path=LaunchFavorite}"
Content="{Binding Path=ModuleDisplayName}"
Cursor="Hand"
FontSize="{StaticResource UxLevel_5}"
Padding="24 12 2 12"
ToolTip="{Binding Path=ModuleDisplayName}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
I have another button called "Launch". Its basic functionality is to launch the tasks as per the selection in the above listbox items so that multiple tasks can be launched.Button XAML:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}">
Issue:
I want to enable/disable this launch button if atleast one item is selected and disable otherwise.How can i achieve? Pls help with some code snippets.
UPDATE:
Here is the launch command:
public ICommand LaunchCommand { get; private set; }
LaunchCommand = new DelegateCommand<object>(OnLaunch);
internal void OnLaunch(object sender)
{
var nFavItemToLaunchCount = Favorites.Count(favItem => favItem.IsChecked);
if (!IsSessionLimitReached(nFavItemToLaunchCount))
{
foreach (FavoriteItemViewModel favoriteItem in Favorites)
{
if (favoriteItem.IsChecked)
{
favoriteItem.LaunchFavorite.Execute(sender);
}
}
}
}
UPDATE 2:
I made the changes as per Krishna's comment:
LaunchCommand = new DelegateCommand<object>(OnLaunch,ToggleLaunch);
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked)!=0) //Favorites is the itemsource
{
return true;
}
return false;
}
Still the launch button is shown disabled always even when item is selected/checked.
UPDATE 3
After Krishna's further comment, i changed the implementation of the property IsChecked and also implemented INotifyPropertyChanged in the viewmodel.Still no luck!
public class FavoriteItemViewModel:INotifyPropertyChanged
{
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
/// <summary>
/// Handler for property change
/// </summary>
/// <param name="propertyName">Name of property</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There is another viewmodel "FavoriteContainerViewModel" that holds the collection of "FavoriteItemViewModel" and also implements INotifyPropertyChanged. This container view model is the place where i added the code mentioned in UPDATE 2
UPDATE 4:
Implementation of favorites:
public class FavoriteContainerViewModel : ViewModelBase, IModuleView
{
private readonly ObservableCollection<FavoriteItemViewModel> m_Favorites = new ObservableCollection<FavoriteItemViewModel>();
public ObservableCollection<FavoriteItemViewModel> Favorites
{
get { return m_Favorites; }
}
public FavoriteContainerViewModel()
{
LaunchCommand = new DelegateCommand<object>(OnLaunch, ToggleLaunch);
OnLoad();
}
private bool ToggleLaunch(object obj)
{
if (Favorites.Count(i => i.IsChecked) != 0)
{
return true;
}
return false;
//return true;
}
}
Note:ViewModelBase implements INotifyPropertyChanged
Update 5:
Problem now resolved using event based model. Implemented following:
FavoriteViewModel.cs
public event EventHandler ItemChecked;
public bool IsChecked
{
get
{
return m_IsChecked;
}
set
{
m_IsChecked = value;
if (ItemChecked != null)
{
ItemChecked(this, new EventArgs());
}
}
}
FavoriteItemContainerViewModel.cs
private void SubscribeFavoriteItemEvents(FavoriteItemViewModel favorite)
{
favorite.ItemChecked += ToggleLaunchButton;
}
private bool m_IsLaunchEnabled;
public bool IsLaunchEnabled
{
get
{
return m_IsLaunchEnabled;
}
set
{
m_IsLaunchEnabled = value;
OnPropertyChanged("IsLaunchEnabled");
}
}
Property IsLaunchEnabled is binded to the button to enable/disable.
You can enable/disable the Button based on the CheckBox.IsChecked property:
<Button Name="btnLaunch"
Width="80"
Height="25"
HorizontalAlignment="Left"
AutomationProperties.AutomationId="AUI_BtnLaunchForFavorite"
Command="{Binding LaunchCommand}"
Style="{StaticResource LaunchButtonStyle}"
Visibility="{Binding IsRIAMode,
Converter={StaticResource boolToVisibilityInverter}}"
IsEnabled="{Binding ElementName=CheckFavorite, Path=IsChecked}">
I am guessing the launch button is outside the listbox and user clicks it once the checkboxes are checked.
In that case, you need to add a condition in the 'CanExecute' part of your ICommand (LaunchCommand)
Lets assume your ICommand implementation in the constructor is something like
LaunchCommand = new RelayCommand(launchMethod);
private void launchMethod(object obj)
{
//do some work here
}
Now add canExecute part to your command by changing the initialisation
LaunchCommand = new DelegateCommand<object>(OnLaunch,checkCheckboxes);
and add this method
private bool checkCheckboxes(object obj)
{
//YourList is your itemsource
return YourList.Where(i=>i.IsChecked).Any();
}
Just change the above method to suit your needs
Update change your IsChecked property to below
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
if (checkbox1.Checked && !checkbox2.Checked)
{
button1.Enable = true
}
else if (!checkbox1.Checked && checkbox2.Checked)
{
button1.Enable = false
}
Related
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.
I have a WPF Form, that has a text box and a button. I am validating the text box to have only characters. Validation works fine, But I need to disable the button if there are validation error and enable them if there are no validation errors.
Below is my code:
<TextBox Name="tbProductName" Grid.Column="1" HorizontalAlignment="Left" Height="25" Margin="4,9,0,0" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Top" Width="213"
Text="{Binding Path = ProductCode, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,NotifyOnValidationError=True}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
<Button Name ="btnDownload" Content="Download" Grid.Column="2" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="87" Height="24" Click="btnDownload_Click"/>
public class ViewModel: System.ComponentModel.INotifyDataErrorInfo
{
private readonly Dictionary<string, ICollection<string>> _validationErrors = new Dictionary<string, ICollection<string>>();
private readonly Model _productCode = new Model();
public string ProductCode
{
get { return _productCode.ProductCode; }
set
{
_productCode.ProductCode = value;
ValidateModelProperty(value, "ProductCode");
}
}
protected void ValidateModelProperty(object value, string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
PropertyInfo propertyInfo = _productCode.GetType().GetProperty(propertyName);
IList<string> validationErrors =
(from validationAttribute in propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>()
where !validationAttribute.IsValid(value)
select validationAttribute.FormatErrorMessage(string.Empty))
.ToList();
_validationErrors.Add(propertyName, validationErrors);
RaiseErrorsChanged(propertyName);
}
/* Raise the ErrorsChanged for all properties explicitly */
protected void ValidateModel()
{
_validationErrors.Clear();
ICollection<ValidationResult> validationResults = new List<ValidationResult>();
ValidationContext validationContext = new ValidationContext(_productCode, null, null);
if (!Validator.TryValidateObject(_productCode, validationContext, validationResults, true))
{
foreach (ValidationResult validationResult in validationResults)
{
string property = validationResult.MemberNames.ElementAt(0);
if (_validationErrors.ContainsKey(property))
{
_validationErrors[property].Add(validationResult.ErrorMessage);
}
else
{
_validationErrors.Add(property, new List<string> { validationResult.ErrorMessage });
}
}
}
/* Raise the ErrorsChanged for all properties explicitly */
RaiseErrorsChanged("ProductCode");
}
#region INotifyDataErrorInfo members
public event EventHandler<System.ComponentModel.DataErrorsChangedEventArgs> ErrorsChanged;
private void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new System.ComponentModel.DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)
|| !_validationErrors.ContainsKey(propertyName))
return null;
return _validationErrors[propertyName];
}
public bool HasErrors
{
get { return _validationErrors.Count > 0; }
}
#endregion
}
public class Model
{
[Required(ErrorMessage = "You must enter a product code to download.")]
[RegularExpression(#"^[a-zA-Z]+$", ErrorMessage = "The Product Code must only contain letters (a-z, A-Z).")]
public string ProductCode { get; set; }
}
How can I set the isEnabled property of the button to Validation.HasErrors?
Ideally your button would bind it's "Command" property to a public ICommand viewmodel property.
The CanExecute method would be evaluated, returning true or false. The button would be enabled/disabled accordingly.
You can read more about ICommand, along with an implementation of the interface here.
Below are the changes required, assuming your using the RelayCommand (See Fig. 3) implementation described in the aforementioned article.
Register the command
private readonly ICommand _downloadCommand = new RelayCommand(OnDownload, CanDownload);
Used for binding:
public ICommand DownloadCommand { get { return _downloadCommand; } }
Methods to invoke when the command is executed:
private void OnDownload(object parameter) { ... Do your download code here ... }
private bool CanDownload(object parameter) { return HasErrors == false; }
Update your XAML binding:
<Button Content="Download" Grid.Column="2" HorizontalAlignment="Left" Margin="10,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="87" Height="24" Command="{Binding DownloadCommand}" />
I would say use the same technique for the Binding you used for the rest of your code!!
In your xaml
<Button Name ="btnDownload" IsEnabled="{Binding Path= BtnIsEnabled} click="btnDownload_Click"/>
In your code
public bool BtnIsEnabled
{
get { return this._BtnIsEnabled; }
set
{
this._BtnIsEnabled = value;
base.OnPropertyChanged("BtnIsEnabled");
}
}
private bool _BtnIsEnabled;
and then where ever you want to disable your Button go something like
BtnIsEnabled = Validation.HasErrors ? false : true ;
Obviously this is a pseudo scribble. Unless you're after something very specific that I can't seem to find out. Seems you used binding in your code already and I would guess you know how to.
I have a MVVM setup that creates a View on my MainWindow. I am not sure how to know when a user Clicks on a specific Notification Item inside the View. Where would I add the event, or a command to know when that happens?
here are is my MVVM code :
MainWindow
cs:
NotificationViewModel notificationViewModel = new NotificationViewModel();
notificationViewModel.AddNoticiation(new NotificationModel() { Message = "Error", Name = "Station 21" });
NotificationView.DataContext = notificationViewModel;
xaml:
<notification:NotificationView x:Name="NotificationView" />
NotificationModel
public class NotificationModel : INotifyPropertyChanged
{
private string _Message;
public string Message
{
get { return _Message; }
set
{
if (_Message != value)
{
_Message = value;
RaisePropertyChanged("Message");
}
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
RaisePropertyChanged("Name");
}
}
}
public string TimeStamp
{
get { return DateTime.Now.ToString("h:mm:ss"); }
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
NotificationViewModel
public class NotificationViewModel
{
private ObservableCollection<NotificationModel> _Notifications = new ObservableCollection<NotificationModel>();
public ObservableCollection<NotificationModel> Notifications
{
get { return _Notifications; }
set { _Notifications = value; }
}
public void AddNoticiation(NotificationModel notification)
{
this.Notifications.Insert(0, notification);
}
}
NotificationView
<Grid>
<StackPanel HorizontalAlignment="Left" >
<ItemsControl ItemsSource="{Binding Path=Notifications}"
Padding="5,5,5,5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="SlateGray"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=TimeStamp}" />
<TextBlock Grid.Column="1"
Text="{Binding Path=Name}" />
<TextBlock Grid.Column="2"
Text="{Binding Path=Message}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
There's no real selection mechanism built into an ItemsControl. It would probably solve your problem to switch out your ItemsControl for a ListBox.
If you do that, you can bind to SelectedItem, then handle any changes made to SelectedItem using the PropertyChanged event.
Example:
In your view model's constructor:
PropertyChanged += NotificationViewModel_PropertyChanged;
Add a property to your view model to allow the binding:
private string _selectedNotification;
public string SelectedNotification
{
get { return _selectedNotification; }
set
{
if (_selectedNotification != value)
{
_selectedNotification = value;
RaisePropertyChanged("SelectedNotification");
}
}
}
Finally, add the event handler to your view model:
NotificationViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e))
{
if (e.PropertyName = "SelectedNotification") DoStuff();
}
You may find that you don't even need to hook into PropertyChanged if you just want to update another control in your view based on the selected item in your list box. You can just bind directly to the property within xaml.
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" />
I have the following ui items - one checkbox list and one checkbox with toggle all checkboxes in that list -
<DockPanel>
<CheckBox
Name="SelectCheckboxes"
Command="{Binding ToggleCheckBoxes}"
Content="Whatever"/>
</DockPanel>
<DockPanel>
<ListBox Name="MyListBox"
ItemsSource="{Binding Path=MyProperty, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="MyCheckBox"
Content="{Binding myvalue}"
Tag="{Binding mycode}"
IsChecked="{Binding Path=isChecked, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
And here is the MyProperty property -
public ObservableCollection<SomeEntity> MyProperty
{
get { return _someEntities; }
set
{
if (value == _someEntities)
return;
_someEntities = value;
base.OnPropertyChanged("MyProperty");
}
}
And here is a command ToggleCheckBoxes -
public ICommand ToggleCheckBoxes
{
get
{
if (_toggleCheckBoxesCommand == null)
{
_toggleCheckBoxesCommand = new RelayCommand(
param => this.toggleCheckBoxes()
);
}
return _toggleCheckBoxesCommand;
}
}
void toggleCheckBoxes()
{
foreach (var i in MyProperty)
{
if (i.isChecked)
i.isChecked = false;
else
i.isChecked = true;
}
}
When I click on the checkbox to toggle the checkboxes, I can look at the property in the code and see that the isChecked property is changed, but the ListBox does not update to reflect that all items are checked/unchecked.
Does anyone see anything that I am missing that might cause the ListBox not to update?
Thnaks for any thoughts.
Make sure that your isChecked member is actually a property and that SomeEntity implements INotifyPropertyChanged. Something like:
public class SomeEntity : INotifyPropertyChanged {
private bool _isChecked;
public bool isChecked
{
get { return _isChecked; }
set
{
if (value == _isChecked)
return;
_isChecked= value;
this.NotifyPropertyChanged("isChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}