I am currently learning the MVVM design pattern for WPF and I would like to bind my CheckBoxes based on the SelectedItem of my ComboBox which is data binded to a Dictionary where the key is my device serial number and the value is a ViewModel for that device.
Ideally, my SelectedItem should be the value which is IOBoardViewModel.
<ComboBox Name="io_boards_list"
ItemsSource="{Binding IOBoards}" DisplayMemberPath="Key" SelectedValuePath="Value"
HorizontalAlignment="Left" Margin="361,28,0,0" VerticalAlignment="Top" Width="657" Height="35"/>
This is where I am most unclear: how to bind these bool values in my IOBoard Model.
<CheckBox IsChecked="{ Binding Value.io_board.s0 }" Content="Input 0" Canvas.Left="10" Canvas.Top="10" Height="15"/>
<CheckBox IsChecked="{ Binding Value.io_board.s1 }" Content="Input 1" Canvas.Left="10" Canvas.Top="36" Height="15"/>
<CheckBox IsChecked="{ Binding Value.io_board.s2 }" Content="Input 2" Canvas.Left="10" Canvas.Top="63" Height="15"/>
In my IOBoardViewModel I refer to the actual Model which is where I hold my bool value.
public Dictionary<string, IIOBoardViewModel> IOBoards { get; private set; }
public FullIOBoard io_board
{
get { return _io_board; }
}
Here is a snippet from my IOBoard Model:
public bool s0
{
get
{
return _s0;
}
set
{
_s0 = value;
OnPropertyChanged("s0");
}
}
public bool s1
{
get
{
return _s1;
}
set
{
_s1 = value;
OnPropertyChanged("s1");
}
}
public bool s2
{
get
{
return _s2;
}
set
{
_s2 = value;
OnPropertyChanged("s2");
}
}
I would like to know if I have the right idea on how to bind the data together in the MVVM pattern, and if not, I would greatly appreciate insights or advice.
UPDATE:
I have thought of a method that I could use where I bind the SelectedItem of the ComboBox (which would be the Dictionary's identifying serial number to a variable in my ViewModel):
public string SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
}
}
Now I believe I should bind my IsChecked for the CheckBox to
<CheckBox IsChecked="{ Binding IOBoards[SelectedItem].s0 }" Content="Input 0" Canvas.Left="10" Canvas.Top="10" Height="15"/>
However I am still unable to get the checkbox to update even though I've hardcoded one of the signals to always be true:
_io_board.s0 = true;
_io_board.s1 = (inputSignal & 0x02) == 0x02;
you can bind directly to SelectedItem of ComboBox by referring to it by name:
<CheckBox IsChecked="{ Binding SelectedItem.Value.io_board.s0, ElementName=io_boards_list }"
Related
I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"
I am new to WPF and have come across a problem.
I have an MVVM WPF application and I want to implement filtering to my DataGrid. I have tried all possible solutions on the internet, but none of them work for me for some reason. I have created a TextBox and binded it to FilterName. What I want it to do is on every keypress, the value of FilterName should be updated and the filter should be triggered. Unfortunately, the filter triggers only once - when I start the application and by putting a breakpoint in the Set block of FilterName, I have discovered that it never reaches it.
Here is the declaration of the TextBox:
<TextBox
x:Name="FilterName"
MinWidth="150"
Margin="{StaticResource SmallTopBottomMargin}"
Background="Transparent"
BorderThickness="0,0,0,1"
Text="{Binding FilterName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, IsAsync=True}"
TextWrapping="Wrap" />
And here is the ViewModel:
private readonly ObservableCollection<PersonData> _data;
public ICollectionView DataCollectionView { get; }
private string _filterName = string.Empty;
public string FilterName
{
get
{
return _filterName;
}
set
{
_filterName = value;
DataCollectionView.Refresh();
}
}
public MainWindowViewModel(ISampleDataService sampleDataService)
{
//Adding the data here
DataCollectionView = CollectionViewSource.GetDefaultView(_data);
DataCollectionView.Filter = FilterByName;
}
private bool FilterByName(object obj)
{
if (obj is PersonData data)
{
return data.Name.Contains(FilterName, StringComparison.InvariantCultureIgnoreCase);
}
return false;
}
This binding should work provided that the view model with the FilterName property is the DataContext of the parent window:
Text="{Binding DataContext.FilterName, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource AncestorType=Window}}"
Set name of your window to x:Name="_this" and change the TextBox binding:
<TextBox
x:Name="tbFilterName"
DataContext="{Binding ElementName=_this}"
Text="{Binding Path=FilterName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="FilterName_TextChanged"
...
Remove DataCollectionView.Refresh(); call from the FilterName setter, but add
private void FilterName_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
DataCollectionView.Refresh();
}
EDIT
Solution was actually a proper setting of SelectedItem, SelectedValue and SelectedValuePath properties.
<ComboBox
Grid.Column="1"
Padding="5"
DisplayMemberPath="PositionName"
IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding Positions, Mode=OneWay}"
SelectedItem="{Binding SelectedOperatorPosition, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding SelectedOperatorPosition.PositionName}"
SelectedValuePath="PositionName" />
QUESTION
I am trying to bind ComboBox SelectedValue with DataGrid SelectedItem using MVVM pattern.
ComboBox ItemSource is Shifts property. I want it's SelectedValue to be bound with SelectedShift property, which is updated each time user selects another OperatorModel from DataGrid.
Although SelectedOperator setter sets SelectedShift value to SelectedOperator.Shift, ComboBox doesn't get updated.
View
Debugging ViewModel Property setter
My ViewModel:
private Operator selectedOperator;
public Operator SelectedOperator
{
get
{
return selectedOperator;
}
set
{
selectedOperator = value;
if (selectedOperator != null)
{
SelectedShift = selectedOperator.Shift;
}
OnPropertyChanged(nameof(SelectedOperator));
}
}
private Shift selectedShift;
public Shift SelectedShift
{
get
{
return selectedShift;
}
set
{
selectedShift = value;
OnPropertyChanged(nameof(SelectedShift));
}
}
XAML:
<ComboBox
Grid.Column="1"
Padding="5"
DisplayMemberPath="Name"
ItemsSource="{Binding Shifts}"
SelectedValue="{Binding SelectedShift}"
SelectedValuePath="Name" />
i think you should use SelectedItem="{Binding SelectedShift}
I want to databind the selected item of a combobox to a c# property. When i do the following, the property get the value "Suite.Module.RateExperiment.ViewModels.ChamberViewModel"(which is not the value of the selected item in the combobox):
<ComboBox DisplayMemberPath="ChamberName" Grid.Column="0" Grid.Row="1" Height="20" VerticalAlignment="Top" ItemsSource="{Binding ChamberCollection}" SelectedValue="{Binding SelectedChamber}">
</ComboBox>
And c#:
public string SelectedChamber
{
get { return _selectedChamber; }
set
{
_selectedChamber = value;
UpdateChart();
}
}
Am i binding wrong since this property gets this value?
SelectedChamber property should be of type ChamberViewModel, try changing it as below:
public ChamberViewModel SelectedChamber
{
get { return _selectedChamber; }
set
{
_selectedChamber = value;
UpdateChart();
}
}
*Intro: *
I'm working with a standard MVVM framework and I have two listboxes from which I want to be able to select one item from. The listboxes are binding to different ObservableCollections of the same class.
After binding to the ViewModel, I want to be able to represent the selected Item on the same window, from either of the Listbox depending on which item I am selecting.
ViewModel -
private KisesaSearchResultViewModel _selectedPerson;
public KisesaSearchResultViewModel SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
private KisesaSearchResultViewModel _selectedSearch;
public KisesaSearchResultViewModel SelectedSearch
{
get
{
return _selectedSearch;
}
set
{
_selectedSearch = value;
SelectedPerson = value;
OnPropertyChanged("SelectedSearch");
}
}
private KisesaSearchResultViewModel _selectedMatch;
public KisesaSearchResultViewModel SelectedMatch
{
get
{
return _selectedMatch;
}
set
{
_selectedMatch = value;
SelectedPerson = _selectedMatch;
OnPropertyChanged("SelectedMatch");
}
}
XAML -
<ListBox ItemsSource="{Binding Path=MatchedMembers, Mode=OneWay}"
ItemTemplate="{StaticResource SearchResult}"
SelectedItem="{Binding SelectedSearch}">
</ListBox>
<ListBox ItemsSource="{Binding Path=SelectedMatchList, Mode=OneWay}"
ItemTemplate="{StaticResource SearchResult}"
SelectedItem="{Binding SelectedMatch}">
</ListBox>
I want to display information as such:
<TextBlock Grid.Row="1" Text="{Binding Path= SelectedPerson.FullName}" FontSize="18" FontWeight="Bold" Style="{StaticResource PInfo}" />
<TextBlock Grid.Column="0" Grid.Row="2" Style="{StaticResource Info}" Margin="30,0,0,0" Text="{Binding Path=SelectedPerson.Age}"/>
Question:
I need to have SelectedSearch and SelectedPerson because I want to be able to change SelectedPerson independtly. At this point, the SelectedPerson is getting set by the SelectedSearch setter, but it is not binding to the Textblocks. I am using OnPropertyChanged, but do I need to do something else like use an Event Handler? Also, slightly unrelated, but can I restrict a WPF window so that only one item from two different Listboxes is selected at a time?