Binding not triggering Action<> - wpf

I have some code like this
this is a custom datagrid which displays hierarchical data which should close and open.
<UserControl.DataContext>
<loc:Def1 x:Name="initdef1"/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" HorizontalAlignment="Center" VerticalAlignment="Center" >
<data:DataGrid x:Name="_dataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Display, Mode=OneWay}"
SelectionMode="Extended" >
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Col1">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox IsChecked="{Binding IsExpanded, Mode=TwoWay}" Margin="{Binding Path=Level, Converter={StaticResource ConvertToThickness}}"/>
<TextBlock Text="{Binding Cells[0]}" Margin="4" />
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
<data:DataGridTextColumn Header="Col2" Binding="{Binding Cells[1]}" />
</data:DataGrid.Columns>
</data:DataGrid>
with this is def1
this class has the actual logic for all the processing and loading and mapping the data to the grid.
public Def1()
{
_columns = new List<ColumnDef>();
_source = new ObservableCollection<Def2>();
Def2.RowExpanding += new Action<Def2>(RowDef_RowExpanding);
Def2.RowCollapsing += new Action<Def2>(RowDef_RowCollapsing);
}
void RowDef_RowExpanding(Def2 row)
{
foreach (RowDef child in row.Children)
child.IsVisible = true;
OnPropertyChanged("Display");
}
void RowDef_RowCollapsing(Def2 row)
{
foreach (Def2 child in row.Children)
{
if (row.IsExpanded.HasValue && row.IsExpanded.Value)
RowDef_RowCollapsing(child);
child.IsVisible = false;
}
OnPropertyChanged("Display");
}
and this in def2
this class has the logic on how should the rows behave.
public bool? IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
if (_isExpanded.Value)
{
if (RowDef.RowExpanding != null)
RowDef.RowExpanding(this);
}
else
{
if (RowDef.RowCollapsing != null)
RowDef.RowCollapsing(this);
}
}
}
}
The thing is when the checkbox is checked or unchecked nothing happens.

Ok I found the answer from a similar post wpf 4.0 datagrid template column two-way binding problem
So I changed the code to
<CheckBox IsChecked="{Binding Mode=TwoWay, Path=IsExpanded, UpdateSourceTrigger=PropertyChanged}" Margin="{Binding Path=Level, Converter={StaticResource ConvertToThickness}}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" x:Name="checkbox1"/>
Now it works.
But can anybody explain why i needed to set UpdateSourceTrigger=PropertyChanged ?
It's not always required.
Thanks

Related

List View Selected Item Binding in wpf mvvm

I am using a ListView in wpf mvvm pattern whose SelectedItem binding is done to the ViewModel. The problem what I am facing is as soon as I check the checkbox, The SelectedItem binding is not working immediately. It work only when I click again somewhere outside the checkbox and its respective content.
My ListView is like this:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="checkboxHeaderTemplate">
<CheckBox IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=UserControl },Mode=TwoWay}">
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="CheckBoxCell">
<!--<CheckBox Checked="CheckBox_Checked" />-->
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="TextCell">
<TextBlock Text="Usecasename">
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="ButtonCell">
<Button Content="{Binding Path=UsecaseName, Mode=TwoWay}" >
</Button>
</DataTemplate>
</Grid.Resources>
<ListView SelectedItem="{Binding SelectedSection}" ItemsSource="{Binding Path=UsecaseListItems}" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn HeaderTemplate="{StaticResource checkboxHeaderTemplate}"
CellTemplate="{StaticResource CheckBoxCell}" Width="auto">
</GridViewColumn>
<GridViewColumn HeaderTemplate="{StaticResource TextCell}"
CellTemplate="{StaticResource ButtonCell}" Width="auto">
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Grid>
The HomeViewModel with which I am binding the Selected itm of List View is like this:
private UseCase _selectedSection;
public UseCase SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
if (this.SelectedSection.UsecaseName == ("CCS01") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new CCS01();
}
else if (this.SelectedSection.UsecaseName == ("CCS02") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new CCS02();
}
else if (this.SelectedSection.UsecaseName == ("ECS52") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new ECS52();
}
else
this.ContentWindow = new Default();
OnPropertyChanged("SelectedSection");
}
}
and The UseCase class is this:
public class UseCase: BaseNotifyPropertyChanged
{
public string UsecaseName { get; set; }
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
Please suggest what correction should I do so that, It should hit the binding directly as I check the Checkboxes.
You should change the checkbox binding of the CheckBoxCell to something like this :
<DataTemplate x:Key="CheckBoxCell">
<!--<CheckBox Checked="CheckBox_Checked" />-->
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</CheckBox>
</DataTemplate>
And for this work you need to put SelectionMode to Single
<ListView SelectedItem="{Binding SelectedSection}"
ItemsSource="{Binding Path=UsecaseListItems}" SelectionMode="Single">
And do this in your viewmodel
private UseCase _selectedSection;
public UseCase SelectedSection
{
get { return _selectedSection; }
set
{
DeSelectAll();
_selectedSection = value;
_selectedSection.IsSelected = true;
OnPropertyChanged("SelectedSection");
}
}
private void DeSelectAll()
{
foreach (var item in UsecaseListItems)
{
item.IsSelected = false;
}
}

select all checkbox binding in mvvm

I am working on 'Select All' checkbox in datagrid header in wpf using mvvm pattern. On clicking the checkbox, all the checkboxes gets checked and on uncheking it, the reverse happens.
But I am unable to fetch and bind the selected items to the View Model.
My code is like this
<DataGrid Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<CheckBox x:Name="headerCheckBox" IsChecked="{Binding Path=MainWindowViewModel.AllSelected, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Command="{Binding DoStuffCommand}" CommandParameter="{Binding ElementName=UserCaseListControl}"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" HorizontalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=headerCheckBox, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
ViewModel is like:
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
OnPropertyChanged("IsSelected");
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}
With your code, there will be no direct binding of selected items. As all your checkboxes were bind to headerCheckBox.IsChecked.
You could bind to your UsecaseListItem.IsSelected instead, and find another way to to execute selectAll:
e.g. Define the UseCase that inherit ObservableObject and use your above code in AllSelected to change each item's IsSelected property.
Or use a Command (for example) in your headerCheckBox.

WPF Autocompletebox MVVM how to get selectedItem from another control?

I try to develop a WPF user control (image below) that allows me to search customer by name or Number. To achieve this, I use MVVM Approach (MVVM Light).
How can i achieve the following goals at the same time :
1°) If I know the number I can enter it in the textbox and the name will automatically appear in the AutoCompleteBox
2°) If the customer number is unknown ,i use autocompletebox to search by name, then the Textbox must contain the number of selected customer .
The problem is :
If i bind the textbox to the Autocompletebox selectedItem, second goal is achieved but not the first. When i press Enter key in the textbox, even when it contains valid code, I have always message "Customer not found." and the customer code is empty as if it is not bound to AutocompleteBox SelectedItem.
Below code :
//snipped from user control
<TextBox Text="{Binding ElementName=custName, Path=SelectedItem.CodeClient, UpdateSourceTrigger=PropertyChanged}" Width="80" Margin="8,8,0,8">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<gs:EventToCommand PassEventArgsToCommand="True"
Command="{Binding GetClientCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Margin="0,8,2,8" Width="20" Command="{Binding SearchCommand}"/>
<Label Content="Name" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,5,0,5"/>
<controls:AutoCompleteBox x:Name="custName"
ItemsSource="{Binding ListClients}"
MinimumPrefixLength="3"
MinimumPopulateDelay="200"
ValueMemberBinding="{Binding Path=NomClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Text="{Binding Path=ClientName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedItem="{Binding ElementName=this, Path=CodeClient, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
FilterMode="Contains"
IsTextCompletionEnabled="True"
Width="400"
Margin="2,8,5,8"
>
<controls:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock Text="{Binding Path=CodeClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="5" />
<TextBlock Text="{Binding Path=NomClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="5"/>
</StackPanel>
</DataTemplate>
</controls:AutoCompleteBox.ItemTemplate>
</controls:AutoCompleteBox>
</StackPanel>
//light class for customer contains only code and name for use in autocompletebox
public class ClientReduit : ObservableObject
{
string _codeclient;
public string CodeClient
{
get { return _codeclient; }
set { _codeclient = value;
RaisePropertyChanged("CodeClient");
}
}
string _nomclient;
public string NomClient
{
get { return _nomclient; }
set { _nomclient = value;
RaisePropertyChanged("NomClient");
}
}
long? _clientid;
public long? Clientid
{
get { return _clientid; }
set { _clientid = value;
}
}
}
//-------ViewModel de UserControl CustomerSearch
public class ViewModelRechercheClient : ObservableObject
{
Client _client;
string _codeClient;
string _nomClient;
bool _clientFound = false;
public bool ClientFound
{
get { return _clientFound; }
set { _clientFound = value;}
}
ObservableCollection<ClientReduit> _listClients;
public ObservableCollection<ClientReduit> ListClients
{
get
{
if (_listClients == null)
_listClients = new ObservableCollection<ClientReduit>();
return _listClients;
}
set
{
_listClients = value;
RaisePropertyChanged("ListClients");
}
}
public Client CurrentClient
{
get { return _client; }
set { _client = value; }
}
public string ClientName
{
get { return _nomClient; }
set { _nomClient = value;
RaisePropertyChanged("ClientName");
}
}
public string ClientCode
{
get { return _codeClient; }
set { _codeClient = value;
RaisePropertyChanged("ClientCode");
}
}
//Constructor
public ViewModelRechercheClient()
{
//Load customers
ListClients = new ObservableCollection<ClientReduit>((from c in (((App)Application.Current).GetListClient())
select new ClientReduit
{
CodeClient = c.r01_codcli,
NomClient = c.r01_nomcli.Trim(),
Clientid = c.CLIENTID
}).ToList());
}
//Command for TextBox PreviewkeyDown -> Key.Enter -->
ICommand _getClient;
//---------------------------------------------------------------------------------
//Commande de recherche client lors de l'entrée d'un code client et de l'appui sur
//la touche entrée
//---------------------------------------------------------------------------------
public ICommand GetClientCommand
{
get
{
if (_getClient == null)
_getClient = new RelayCommand<KeyEventArgs>(GetClientCommandExecute);
return _getClient;
}
}
private void GetClientCommandExecute(KeyEventArgs e)
{
bool processIt = false;
ClientFound = false;
if (e != null && e.Key == Key.Enter)
processIt = true;
if (e == null || processIt == true)
{
ILogger _currentLog = ((App)Application.Current).GetCurrentLogger();
using (UnitOfWork cx = new UnitOfWork(_currentLog))
{
ClientRepository _clientRepository = new ClientRepository(cx, _currentLog);
IClientManagementService cms = new ClientManagementService(_currentLog, _clientRepository);
CurrentClient = cms.FindById(ClientCode);
if (CurrentClient != null)
{
ClientFound = true;
ClientCode = CurrentClient.r01_codcli;
ClientName = CurrentClient.r01_nomcli;
}
else
{
ClientFound = false;
Messenger.Default.Send(new APPX.Presentation.Messages.DialogMessage(ClientCode + " : Customer not found."));
}
}
}
}
I think the Question lies in how to get the Customer Number from AutoCompleteBox SelectedItem while respecting MVVM Approach ?
thank you in advance.
I found a solution, but I do not know if it is the best, but it works well for me :
Simply, i add a command that handle SelectionChanged event. in this command i get selectedItem and i assign it to ClientCode (bound to textBox)
below the code of a usercontrol
<StackPanel Orientation="Horizontal">
<Label Content="Number" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,5,0,5" />
<!-- <TextBox Text="{Binding ElementName=custName, Path=SelectedItem.CodeClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Width="80"
Margin="8,8,0,8">-->
<TextBox x:Name="custCode" Text="{Binding ClientCode, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="8,8,0,8" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<gs:EventToCommand PassEventArgsToCommand="True"
Command="{Binding GetClientCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Margin="0,8,2,8" Width="20" Command="{Binding SearchCommand}"/>
<Label Content="Name" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,5,0,5"/>
<controls:AutoCompleteBox x:Name="custName"
ItemsSource="{Binding ListClients}"
MinimumPrefixLength="3"
MinimumPopulateDelay="200"
ValueMemberBinding="{Binding Path=NomClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Text="{Binding Path=ClientName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedItem="{Binding ElementName=this, Path=CodeClient, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
FilterMode="Contains"
IsTextCompletionEnabled="True"
Width="400"
Margin="2,8,5,8"
SelectionChanged="custName_SelectionChanged"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<gs:EventToCommand PassEventArgsToCommand="True"
Command="{Binding SetCodeClientCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<controls:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="Auto">
<TextBlock Text="{Binding Path=CodeClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="5" />
<TextBlock Text="{Binding Path=NomClient, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="5"/>
</StackPanel>
</DataTemplate>
</controls:AutoCompleteBox.ItemTemplate>
</controls:AutoCompleteBox>
</StackPanel>
and the command is as follows :
public ICommand SetCodeClientCommand
{
get
{
if (_setCodeClient == null)
_setCodeClient = new RelayCommand<SelectionChangedEventArgs>(SetCodeClientCommandExecute);
return _setCodeClient;
}
}
private void SetCodeClientCommandExecute(SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
ClientCode = (((ClientReduit)e.AddedItems[0]).CodeClient);
ClientFound = true;
}
}

One combo box filtering another combo box in a WPF Datagrid

I have a datagrid which has two combo box columns in it. The first combo box is a list of PersonnelTypes. Depending on which PersonnelType is selected, the second combo box should fill up with a list of Resources that match the selected PersonnelType
The problem is, lets say I have two rows of data, if I change the PersonnelType of one row, the datagrid will set the itemsource for all of the Resources in every row. I only want it to filter the row that I am in, not all the rows.
Here's the xaml for the part of the datagrid that has the combo boxes:
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the xaml for the whole data grid (just in case you need to see it):
<DataGrid AutoGenerateColumns="False" CanUserSortColumns="False" CanUserDeleteRows="True" IsReadOnly="True" Background="LightGray" CanUserAddRows="False" Margin="5" SelectedItem="{Binding SelectedLA_JobPersonnel}" ItemsSource="{Binding LA_Personnel}" Grid.ColumnSpan="4" MouseDoubleClick="DataGrid_MouseDoubleClick_1">
<DataGrid.Resources>
<ViewModels:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Personnel Type" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelTypes" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" SelectionChanged="cmbPersonnelTypes_SelectionChanged" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Name="cmbPersonnelName" FontWeight="Bold" ItemsSource="{Binding ViewModel.ResourcesToChooseFrom, RelativeSource={RelativeSource AncestorType=Window},UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Resource, Mode=TwoWay}" SelectedValuePath="Refno" DisplayMemberPath="Name" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn> <DataGridTemplateColumn Header="Date Out" Width="20*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateOut, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Date In" Width="20*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Background="LightGray" FontWeight="Bold" Text="{Binding DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}, StringFormat={}{0:MMM-dd-yyyy hh:ss tt}}">
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Toolkit:DateTimePicker Background="LightGray" FontWeight="Bold" Value="{Binding Path=DateIn, Mode=TwoWay, Converter={StaticResource thisNullDateConverter}}" Format="Custom" FormatString="MMM dd yyyy hh:ss tt"></Toolkit:DateTimePicker>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here is the code behind for the xaml (xaml.cs):
public JobEditorViewModel ViewModel
{
get { return viewModel; }
}
private void cmbPersonnelTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var combobox = sender as ComboBox;
if (combobox != null)
{
var selectedPersonnelType = combobox.SelectedItem as PersonnelType;
viewModel.SetResourcesToChooseFrom(selectedPersonnelType);
}
}
Here is the code in the viewModel:
public BindingList<PersonnelType> PersonnelTypes
{
get; set;
}
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
private BindingList<Resource> _resourcesToChooseFrom;
public BindingList<Resource> ResourcesToChooseFrom
{
get { return _resourcesToChooseFrom; }
set
{
_resourcesToChooseFrom = value;
NotifyPropertyChanged("ResourcesToChooseFrom");
}
}
public void SetResourcesToChooseFrom(PersonnelType personnelType)
{
ResourcesToChooseFrom =
new BindingList<Resource>(_db.Resources.Where(r => r.Head == personnelType.Head && r.Refno > 2).OrderBy(r=>r.Name).ToList());
}
If you need to see more, let me know
Well, with some help from a colleague here at work, we figured out what I needed to do. Multibinding is the answer. First off we kind of hacked around so that the two combo boxes could be in the same column by placing them both in a grid and placing the grid in the one column. So now both combo boxes can see each other because they are in the same DataGridTemplateColumn. Before, we couldn't get them to see each other because they lost scope of each other in being two separate DataGridTemplateColumns.
Here's what we did in the xaml:
<DataGridTemplateColumn Header="Personnel Type-Name" Width="Auto" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding PersonnelType.Description}"/>
</Border>
<Border Grid.Column="1" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Resource.Name}"/>
</Border>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox Name="cmbPersonnelTypes" Grid.Column="0" FontWeight="Bold" ItemsSource="{Binding ViewModel.PersonnelTypes, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding PersonnelType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Description" />
<ComboBox Name="cmbPersonnelName" Grid.Column="1" FontWeight="Bold" SelectedItem="{Binding Resource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Refno" DisplayMemberPath="Name" >
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilteredPersonnelConverter}">
<Binding Path="ViewModel.AvailablePersonnel" RelativeSource="{RelativeSource AncestorType=Window}"/>
<Binding Path="SelectedItem" ElementName="cmbPersonnelTypes"/>
<Binding Path="ViewModel.SelectedGlobalResourceViewOption" RelativeSource="{RelativeSource AncestorType=Window}"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
You'll notice there is a ValueConverter in the MultiBinding called FilteredPersonnelConverter. This value converter does all the filtering for me. Here's the code for that:
public class FilteredPersonnelListValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var allResources = values[0] as IList<Resource>;
var personnelType = values[1] as PersonnelType;
var selectedGlobalResourceView = values[2] as ResourceViewOption;
if (personnelType == null)
return allResources;
if(selectedGlobalResourceView.ResourceViewTitle=="Regional")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.Location.Region.RegionID.Trim()==SettingsManager.OpsMgrSettings.Region.Trim()).OrderBy(r => r.Name).ToList();
if (selectedGlobalResourceView.ResourceViewTitle == "Local")
return allResources.Where(r => r.Head == personnelType.Head && r.Obsolete == false && r.LocnID.Trim() == SettingsManager.OpsMgrSettings.LOCNCODE.Trim()).OrderBy(r => r.Name).ToList();
return allResources.Where(r => r.Head == personnelType.Head &&r.Obsolete==false).OrderBy(r => r.Name).ToList();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So if anyone else is doing something like this, look into Multibinding, it will change your life
When the user changes the PersonelType dropdown in the view, the ViewModel should then filter the list of Resources (which should update the second dropdown box with the correct information).
Really your view model just needs to be set up to respond to changes on the view. That's what it boils down to.
Looking here:
public JobEditorViewModel(int jobid, string region, DataAccessDataContext db, ServiceUserControlViewModel serviceViewModel)
{
PersonnelTypes = new BindingList<PersonnelType>(_db.PersonnelTypes.OrderBy(p => p.Head).ThenBy(p => p.Description).ToList());
}
it looks like you set the personel types in the constructor, but you aren't responding to user changes. You need to do that in the PersonelType Binding in your view model.
EDIT
I see now that you're handling the selection changed. But really I think this should be done in the view model itself. Specifically because you actually access the viewmodel from the view to do make your changes.
Example
So here's my VM:
class ViewModel : INotifyPropertyChanged
{
DispatcherTimer timer = new DispatcherTimer();
public ViewModel()
{
// Create my observable collection
this.DateTimes = new ObservableCollection<DateTime>();
// Every second add anothe ritem
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
// This adds to the collection
this.DateTimes.Add(DateTime.Now);
}
public ObservableCollection<DateTime> DateTimes { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
}
and My View:
<ListBox ItemsSource="{Binding DateTimes}"/>
Notice that I don't rebuild the collection. I just set it once, and then change it's size as neccesary.

Selectionchanged event firing for every row

I am using a cascading comboboxes inside datagrid.I am able to get the datas based on selectionchanged but that event is firing for every row.
Here is my code:
<sdk:datagridtemplatecolumn header="Category" width="110">
<sdk:datagridtemplatecolumn.celltemplate>
<datatemplate>
<combobox foreground="Black" height="30" isenabled="{Binding Source={StaticResource EffortViewModel}, Path=ComboBoxStatus}" itemssource="{Binding Source={StaticResource EffortViewModel},Path=ProjTypeTaskCtry}" displaymemberpath="TaskCtgyName" selectedvaluepath="TaskCtgy_FK" selectedvalue="{Binding Source={StaticResource EffortViewModel}, Path=TaskCtgy_FKField,Mode=TwoWay}" />
</datatemplate>
</sdk:datagridtemplatecolumn.celltemplate>
</sdk:datagridtemplatecolumn>
<sdk:datagridtemplatecolumn header="SubCategory" width="110">
<sdk:datagridtemplatecolumn.celltemplate>
<datatemplate>
<combobox foreground="Black" height="30" isenabled="{Binding Source={StaticResource EffortViewModel}, Path=ComboBoxStatus}" itemssource="{Binding Source={StaticResource EffortViewModel},Path=SubCtry,Mode=OneWay}" displaymemberpath="TaskSubCtgyName" selectedvaluepath="{Binding TaskSubCtgy_PK, Mode=TwoWay}" selectedvalue="{Binding TaskSubCtgy_FKField,Mode=OneTime}" selectedindex="{Binding TaskSubCtgy_FKField}" />
</datatemplate>
</sdk:datagridtemplatecolumn.celltemplate>
</sdk:datagridtemplatecolumn>
I had the same problem in Silverlight MVVM. I found a solution for this from somewhere. Hope this will help you.
namespace Test
{
public class ComboBoxSelectionChange : TriggerAction<DependencyObject>
{
public ComboBoxSelectionChange()
{
}
public ComboBox DayComboBox
{
get { return (ComboBox)GetValue(DayComboBoxProperty); }
set { SetValue(DayComboBoxProperty, value); }
}
public static readonly DependencyProperty DayComboBoxProperty =
DependencyProperty.Register("DayComboBox",
typeof(ComboBox),
typeof(ComboBoxSelectionChange),
new PropertyMetadata(null, OnDayComboBoxPropertyChanged));
private static void OnDayComboBoxPropertyChanged(DependencyObjectd, DependencyPropertyChangedEventArgs e)
{
var source = d as ComboBoxSelectionChange;
if (source != null)
{
var value = (ComboBox)e.NewValue;
}
}
protected override void Invoke(object o)
{
if (this.DayComboBox != null)
{
//this method will execute when the selection is changed
}
}
}
}
Use the Test namespace in Usercontrol assembly
xmlns:Common="clr-namespace:Test"
<UserControl.Resources>
<Common:ComboBoxSelectionChange x:Name="ComboBoxItem"/>
</UserControl.Resources>
<DataTemplate x:Key="EditMondayDataTemplate">
<ComboBox x:Name="cmbMonday" Height="26" Margin="3" ItemsSource="{Binding Monday,Mode=OneTime}" DisplayMemberPath="displayText" SelectedItem="{Binding Path=MonSelected,Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Common:ComboBoxSelectionChange DayComboBox="{Binding ElementName=cmbMonday}" TextParam="Monday"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>

Resources