I want to implement a checkbox which functions as a select-all / unselect-all checkbox but I am getting some problems with the binding. I am not really new to XAML and WPF so it is a mistery to me why my code is not working. I hope you can help. This is what I am doing:
First of all, I use MVVMLight and the event-to-command tags.
My view is called SetupView.xaml and my view-model is called SetupViewModel.cs
public class SetupViewModel : ViewModelBase
{
private List<FilterOptions> m_informationToShow;
private FilterOptions m_currentSelection;
public List<FilterOptions> InformationToShow
{
get { return m_informationToShow; }
set
{
m_informationToShow = value;
RaisePropertyChanged("InformationToShow");
RaisePropertyChanged("InformationToShowCount");
}
}
public FilterOptions CurrentSelection
{
get { return m_currentSelection; }
set
{
m_currentSelection = value;
RaisePropertyChanged("CurrentSelection");
}
}
}
The FilterOptions-object is my model object. Because you will need some of it it to understand the problem, here it show the part you need to understand the problem:
public class FilterOptions
{
private string m_projectName;
private BugsFilter m_bugsFilter;
private BuildsFilter m_buildsFilter;
private ChangeSetsFilter m_changeSetsFilter;
private ProgressInfoFilter m_progressInfoFilter;
private RisksFilter m_risksFilter;
private bool m_projectHealthFilter;
public bool AllFilterValues
{
get
{
if (m_bugsFilter.AtLeastOneFieldEnabled() ||
m_buildsFilter.AtLeastOneFieldEnabled() ||
m_changeSetsFilter.AtLeastOneFieldEnabled() ||
m_progressInfoFilter.AtLeastOneFieldEnabled() ||
m_risksFilter.AtLeastOneFieldEnabled() ||
m_projectHealthFilter
)
{
return true;
}
else
{
return false;
}
}
set
{
if (value == false)
{
m_bugsFilter.NoInformation();
m_buildsFilter.NoInformation();
m_changeSetsFilter.NoInformation();
m_progressInfoFilter.NoInformation();
m_risksFilter.NoInformation();
m_projectHealthFilter = false;
}
else
{
m_bugsFilter.CompleteInformation();
m_buildsFilter.CompleteInformation();
m_changeSetsFilter.CompleteInformation();
m_progressInfoFilter.CompleteInformation();
m_risksFilter.CompleteInformation();
m_projectHealthFilter = true;
}
}
}
I will proceed with my view:
<UserControl.Resources>
<viewModels:SetupViewModel x:Key="thisViewModel"></viewModels:SetupViewModel>
<DataTemplate x:Key="ProjectEntryTemplate">
<Border Margin="75,20,5,0">
<CheckBox Name="naam" Content="{Binding ProjectName}"
FontFamily="Segoe UI"
FontWeight="Light"
FontSize="24"
IsChecked="{Binding AllFilterValues}"
DataContext="{Binding}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<mvvm:EventToCommand Command="{Binding ProjectListItemCheckedChanged, Source={StaticResource thisViewModel}}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<mvvm:EventToCommand Command="{Binding ProjectListItemCheckedChanged, Source={StaticResource thisViewModel}}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</Border>
</DataTemplate>
</UserControl.Resources>
As you can see, I have a datatemplate which I am using in a listbox:
<ListBox ItemsSource="{Binding InformationToShow}"
ItemTemplate="{StaticResource ProjectEntryTemplate}"
SelectedIndex="0"
BorderThickness="0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding SelectionListboxChanged}"
PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Besides from that, I have in the same window, in another grid on the right side, a lot of checkboxes. They all correspond to a given filter from the FilterOptions object. This is what I am doing in my view:
<StackPanel Orientation="Vertical"
Margin="0,5,0,5">
<CheckBox Name="activeBugs" Content="Active bugs"
FontFamily="Segoe UI"
FontWeight="Light"
FontSize="22"
IsChecked="{Binding CurrentSelection.BugsFilter.ActiveBugs}">
</CheckBox>
<CheckBox Name="resolvedBugs" Content="Resolved bugs"
FontFamily="Segoe UI"
FontWeight="Light"
FontSize="22"
IsChecked="{Binding CurrentSelection.BugsFilter.ResolvedBugs}"/>
<CheckBox Name="bugTrend" Content="Bug trend"
FontFamily="Segoe UI"
FontWeight="Light"
FontSize="22"
IsChecked="{Binding CurrentSelection.BugsFilter.BugTrend}"/>
</StackPanel>
Last but not least, this are the command functions which I have in my view-model:
RelayCommand m_selectionChanged;
public ICommand SelectionListboxChanged
{
get
{
if (m_selectionChanged == null)
m_selectionChanged = new RelayCommand(param => SelectionListboxChangedExec(param), param => true);
return m_selectionChanged;
}
}
private void SelectionListboxChangedExec(object param)
{
SelectionChangedEventArgs e = (SelectionChangedEventArgs)param;
ListBox b = (ListBox)e.Source;
CurrentSelection = (FilterOptions)b.SelectedItem;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RelayCommand m_projectCheckedChanged;
public ICommand ProjectListItemCheckedChanged
{
get
{
if (m_projectCheckedChanged == null)
m_projectCheckedChanged = new RelayCommand(param => ProjectListItemCheckedChangedExec(param), param => true);
return m_projectCheckedChanged;
}
}
private void ProjectListItemCheckedChangedExec(object param)
{
RoutedEventArgs e = (RoutedEventArgs)param;
CheckBox checkBox = (CheckBox)e.Source;
FilterOptions dataContext = (FilterOptions)checkBox.DataContext;
if ((bool)checkBox.IsChecked)
dataContext.AllFilterValues = true;
else
{
dataContext.AllFilterValues = false;
}
//var expression = checkBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
//expression.UpdateSource();
}
I really cant find the problem. Is there anyone who can help me?
Your help will be much appreciated !!
Thank you all in advance !
Why do you have to do it with a command? bind the checkbox to a BindingOption class that internally notifies the parent ViewModel, this notification really doesn't belong in the view, but to the view-model.
The ViewModel isn't supposed to know that the view uses a CheckBox.
So what u should do have the individual options have a notifying IsSelected property bound to the CheckBoxes, provide each of them with a reference to the parent ViewModel, and notify the parent when current IsSelected was changed. Doing this in the View, although you can find many way to do it, you're not supposed to, MVVM is about separating view-tasks from viewmodel-tasks.
This is way also gives you control on "Select all", "Deselect all" or "Invert selection" etc.
Related
I'm trying to pass items from my Combobox (which is binded to my Model object's lists) to my button. My problem is that I'm new to Caliburn.Micro + WPF and not quite sure how to subscribe/pass the desired values to my button (like sending strings of a PropetyName to a Button(string propetyName)).
ViewModel code:
class ShellViewModel : Screen
{
private DataModel _fileInFolder;
private BindableCollection<DataModel> _data;
public ShellViewModel()
{
// .GetData() preforms the objects' initialization
DataModel dataOutput = new DataModel();
Data = new BindableCollection<DataModel>(dataOutput.GetData());
}
public BindableCollection<DataModel> Data
{
get
{
return _data;
}
set
{
_data = value;
NotifyOfPropertyChange(() => Data);
}
}
public DataModel FileInFolder
{
get { return _fileInFolder; }
set
{
_fileInFolder = value;
NotifyOfPropertyChange(() => FileInFolder);
}
}
//This is where the items will be passed to.
public void OpenFile()
{
}
}
XAML code:
<Grid>
<!-- Folders -->
<ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding FileInFolder}"
HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Folders}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Files -->
<ComboBox x:Name="FileInFolder_Files"
HorizontalAlignment="Left" Margin="280,10,0,0" VerticalAlignment="Top" Width="250"/>
<!-- Open File -->
<Button x:Name="OpenFile"
Content="Open File" HorizontalAlignment="Left" Margin="560,10,0,0" VerticalAlignment="Top" Width="90">
</Button>
</Grid>
Sorry if my description is vague/missing more clarification, I'm a new user here!
FileInFolder is the selected item of the combo.
OpenFile is in the same class and can therefore reference FileInFolder.
public void OpenFile()
{
var whatever = FileInFolder.SomeProperty;
// etc
}
You probably want some null checking in there.
I have an employee table I filtered from Location with a combobox in WPF MVVM.
like that:
in xaml:
<ComboBox Grid.Column="2" ItemsSource="{Binding Locations}" SelectedItem="{Binding SelectedLocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding LocationFilterCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
In ViewModel:
Locations = new ObservableCollection<string>()
{ "All","A","B"};
and in LocationFilterCommand
#region LocationFilterCommand
private DelegateCommand _locationFilterCommand;
public DelegateCommand LocationFilterCommand
{
get { return _locationFilterCommand ?? (_locationFilterCommand = new DelegateCommand(CanLocationFilter, LocationFilter)); }
}
private bool CanLocationFilter()
{
return true;
}
private void LocationFilter()
{
ParticularEntries = ParticularEntries.Where(p =>p.Region.Location==_selectedLocation);
}
I dont have problem with when I select A location or when I select B location they comes to screen.But I dont know how to take all values together with "All" item in combobox?
Hint:All is not a location type.All need all locations employee together come to screen.But I dont know how to this filter ?
thank you for all your helps.
I have a combo box that is editable, and i have a button that is enable when SelectedReplacement that binds to the combobox is not null and disable when it is. When it's null, i would input some random text to make the button enable, the problem is it wouldn't become enable when there I input text. making the Mode TwoWay doesn't help. i assumed setting the propertychangedevent would bind the new text to SelectedReplacement, but Im wrong, so any help is appreciated.
<ComboBox ItemsSource="{Binding SelectedError.Suggestions}"
Text="{m:Binding Path=SelectedError.SelectedReplacement, Mode=TwoWay}"
IsEditable="True"
HorizontalAlignment="Stretch"/>
i also tried to get the propertychanged
private void ViewModelPropertyChanged(SpellcheckViewModel sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(sender.SelectedError.SelectedReplacement))
{
_correctCommand?.Refresh();
}
}
I try to write a demo project to meet your requirement.
Mainly, the enable state is controlled by another boolean property IsButtonEnabled in the view model, the value for that property is controlled by InputText property which is controlled by the text you input in the ComboBox control.
Here is the UI:
<StackPanel Margin="10">
<ComboBox
x:Name="cmb"
IsEditable="True"
ItemsSource="{Binding AllItems}"
TextBoxBase.TextChanged="cmb_TextChanged"
TextSearch.TextPath="Name">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox
x:Name="hiddenTextBox"
Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="Collapsed" />
<Button
x:Name="btn"
Margin="0,10,0,0"
Content="Show message"
IsEnabled="{Binding IsButtonEnabled}" />
</StackPanel>
And here is the main logic in the view model:
public ObservableCollection<Item> AllItems
{
get { return _allItems; }
set { _allItems = value; this.RaisePropertyChanged("AllItems"); }
}
public bool IsButtonEnabled
{
get { return _isButtonEnabled; }
set { _isButtonEnabled = value; this.RaisePropertyChanged("IsButtonEnabled"); }
}
/// <summary>
/// When InputValue changed, change the enable state of the button based on the current conditions
/// </summary>
public string InputText
{
get { return _inputText; }
set
{
_inputText = value;
this.RaisePropertyChanged("InputText");
// You can control the enable state of the button easily
if (AllItems.Any(item => item.Name == value))
{
// SelectedItem is not null
IsButtonEnabled = true;
}
else if (!string.IsNullOrEmpty(value))
{
// SelectedItem is null
IsButtonEnabled = true;
}
else
{
IsButtonEnabled = false;
}
}
}
Finally, here is the project: ComboBoxDemo
I'm trying to folow the mvvm pattern. When using galasoft EventToCommand i'm getting then following error:
The best overloaded method match for 'GalaSoft.MvvmLight.Command.RelayCommand.RelayCommand(System.Action)' has some invalid arguments...
Code from my XAML:
<toolkit:DatePicker Header="Select Date"
ValueStringFormat="{}{0:D}"
HorizontalAlignment="Left" Margin="0,126,0,0"
Name="datePicker1"
VerticalAlignment="Top" FontFamily="Verdana"
FontSize="22" Width="450">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand PassEventArgsToCommand="True"
Command="{Binding DateSelection}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</toolkit:DatePicker>
In the modelview:
public MainViewModel()
{
DateSelection = new RelayCommand<DateTimeValueChangedEventArgs>(time_Call);
}
public RelayCommand<DateTimeValueChangedEventArgs> DateSelection
{
get;
set;
}
void time_Call(object sender, DateTimeValueChangedEventArgs e)
{
}
I'm blank!
Can you Two-Way bind to the Value property instead? This would simplify things and let you use the true power of XAML and MVVM... binding.
<toolkit:DatePicker Header="Select Date"
ValueStringFormat="{}{0:D}"
HorizontalAlignment="Left" Margin="0,126,0,0"
Name="datePicker1"
VerticalAlignment="Top" FontFamily="Verdana"
FontSize="22" Width="450"
Value={Binding SelectedDate, Mode=TwoWay}" />
The view model
private DateTime selectedDate;
public DateTime SelectedDate
{
get
{
return this.selectedDate;
}
set
{
if (this.selectedDate != value)
{
this.selectedDate = value;
this.RaisePropertyChanged("SelectedDate");
}
}
}
public MainViewModel()
{
// initialize to today being selected
this.SelectedDate = DateTime.Now;
// the property changed might not be necessary if you are just trying to get the new value
this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(MainViewModel_PropertyChanged);
}
void MainViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName="SelectedDate")
{
// do something if needed
}
}
I'm using the MVVM Light Toolkit with Silverlight.
On my UserControl I have a ListBox that displays a list of files. Each file has a delete image next to the file name. In the DataTemplate for the listbox I have an image (or can use a button) and a TextBlock.
So I want to capture using the event when the user will clicks on the image(or button with image) to remove the file from the list of files.
But I cannot seem to capture the event. Maybe this is due to having the SelectedItem Event on the listbox?
public class MainViewModel : ViewModelBase
{
#region Properties
public const string SelectedListBoxFilePropertyName = "SelectedUploadFile";
private UploadFile _selectedUploadFile = null;
public UploadFile SelectedUploadFile
{
get
{
return _selectedUploadFile;
}
set
{
if (_selectedUploadFile == value)
return;
_selectedUploadFile = value;
RaisePropertyChanged(SelectedListBoxFilePropertyName);
}
}
public const string UploadFilesPropertyName = "UploadFiles";
private ObservableCollection<UploadFile> _uploadFiles = new ObservableCollection<UploadFile>();
public ObservableCollection<UploadFile> UploadFiles
{
get
{
return _uploadFiles;
}
set
{
if (_uploadFiles == value)
return;
_uploadFiles = value;
RaisePropertyChanged(UploadFilesPropertyName);
}
}
#endregion
public static ICommand BrowseCommand { get; private set; }
public static ICommand DragDropFileCommand { get; private set; }
public static ICommand RemoveCommand { get; private set; }
#region Constructor
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
UploadFiles = new UploadFileContainer().UploadFiles;
}
else
{
// Code runs "for real"
}
WireUpCommands();
}
#endregion
#region Event Handlers
private void OnBrowseFileCommand()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (dialog.Files != null)
AddFiles(dialog.Files);
}
private void OnDropFileCommand(DragEventArgs e)
{
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
AddFiles(files);
}
private void OnRemoveFileCommand()
{
UploadFiles.Remove(_selectedUploadFile);
}
#endregion
#region Private Methods
private void WireUpCommands()
{
BrowseCommand = new RelayCommand(OnBrowseFileCommand);
DragDropFileCommand = new RelayCommand<DragEventArgs>(e => OnDropFileCommand(e));
RemoveCommand = new RelayCommand(OnRemoveFileCommand);
UploadCommand = new RelayCommand(OnClickUploadCommand);
}
#endregion
}
<ListBox Grid.Row="1" Height="214" HorizontalAlignment="Left" AllowDrop="True" Margin="6,26,0,0" Name="UploadFilesListBox" VerticalAlignment="Top" Width="415" ItemsSource="{Binding Path=UploadFiles}" SelectedItem="{Binding Path=SelectedListBoxFile, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding DragDropFileCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Background>
<ImageBrush ImageSource="/FileUploadApplication;component/Resources/dragdrophere.png" Stretch="None" />
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveCommand}">
<Image Source="/FileUploadApplication;component/Resources/delete.png"/>
</Button>
<Image Source="/FileUploadApplication;component/Resources/delete.png">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RemoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image> <TextBlock Text=" " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since your ItemsSource is UploadFiles it's probably sending the event to UploadFile and not the view model the user control is bound to.
Your button is the element of the ItemTemplate. you're binding the listbox ItemsSource to the ObservableCollection. Every Itemtemplate DataContext is no MainViewModel, but UploadFile, which has no RemoveCommand.
I was solving this by adding to every item the parent object using constructor. RemoveCommand was inside the item's ViewModel and insede the remove function i was calling the parent's method to delete the item.
Not sure if that's the best solution but it worked for me.