WPF Autocompletebox MVVM how to get selectedItem from another control? - wpf

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;
}
}

Related

Event Aggregator in Prism usage giving exception

I have to communicate between 2 view models so I am using Event aggregator but I observed the method is calling 2 times when a property is updated and for the first it is working as expected but second time all the elements are null and throwing null reference exception.
Why it is happening I am not triggering it second time.
MainViewModel
private XMLNode NodeSelected;
public XMLNode NodeSelected
{
get { return NodeSelected; }
set
{
nodeSelected = value;
iEventAggregator.GetEvent<AddNewObjectEvent>().Publish(this.NodeSelected);
}
}
UsercontrolViewModel
public UserControlViewModel(IEventAggregator iEventAggregator)
{
this.iEventAggregator = iEventAggregator;
this.iEventAggregator.GetEvent<AddNewGuiSyncObjectEvent>()
.Subscribe(AddXMLNode);
}
AddXMLNODE method related code
private void AddXMLNODE (XMLNode SelectedNode)
{
if (this.CriteriaItem == null) return;
SyncObject currentObj = new SyncObject(SelectedNode);
if (!IsSyncItemNameUnique(currentObj))
{
currentObj.Name = GetUniqueName(currentObj);
}
this.CriteriaItem.SyncObjects.Add(currentObj);
this.SelectedSyncObject = this.CriteriaItem .SyncObjects.LastOrDefault();
}
Here first time "CriteriaItem" is coming correct and newnode is added to collection but again same method is hitting and CriteriaItem is NULL If I remove that null check we are getting exception.
What is the mistake here I am not getting.
The complete code
UserControl ViewModel
namespace Hexagon.SmartUITest.GUISynchronization.ViewModel
{
public class IndividualSyncViewModel : ViewModelBase
{
#region properties
private GUISyncObject selectedSyncObject;
public GUISyncObject SelectedSyncObject
{
get { return selectedSyncObject; }
set
{
selectedSyncObject = value;
this.OnPropertyChanged(() => this.SelectedSyncObject);
iEventAggregator.GetEvent<SelectedSyncObjectEvent>().Publish(this.SelectedSyncObject);
}
}
private SyncCriteriaItem _CriteriaItem;
public SyncCriteriaItem CriteriaItem
{
get { return CriteriaItem; }
set
{
CriteriaItem = value;
OnPropertyChanged(() => this.CriteriaItem);
}
}
private IEventAggregator iEventAggregator;
#endregion
#region constructor
public IndividualSyncViewModel(IEventAggregator iEventAggregator)
{
this.iEventAggregator = iEventAggregator;
this.iEventAggregator.GetEvent<AddNewGuiSyncObjectEvent>()
.Subscribe(AddGUISyncItem);
}
#endregion
private ICommand deleteSyncObjectClick;
public ICommand DeleteSyncObjectClick
{
get
{
if (deleteSyncObjectClick == null) deleteSyncObjectClick = new RelayCommand(DeleteSync);
return deleteSyncObjectClick;
}
set
{
deleteSyncObjectClick = value;
}
}
private void DeleteSync()
{
this.CriteriaItem.SyncObjects.Remove(selectedSyncObject);
selectedSyncObject = this.CriteriaItem.SyncObjects.LastOrDefault();
}
private bool IsItemNameUnique(GUISyncObject SyncObject)
{
if (this.CriteriaItem != null && this.CriteriaItem.SyncObjects.Count == 0)
return true;
foreach (GUISyncObject item in this.CriteriaItem.SyncObjects.ToList())
{
if (item.Name.Equals(SyncObject.Name) && !item.CommandNodeName.Equals(SyncObject.CommandNodeName))
return false;
}
return true;
}
private string GetUniqueName(GUISyncObject syncItem)
{
List<string> itemsNames = this.CriteriaItem.SyncObjects.Select(item => item.Name).ToList();
return CommonNamingRules.GetUniqueName(itemsNames, syncItem.Name);
}
private void AddGUISyncItem(ControlNode SelectedNode)
{
if (this.CriteriaItem == null) return;
GUISyncObject currentObj = new GUISyncObject(SelectedNode);
if (!IsSyncItemNameUnique(currentObj))
{
currentObj.Name = GetUniqueName(currentObj);
}
this.CriteriaItem.SyncObjects.Add(currentObj);
this.SelectedSyncObject = this.CriteriaItem.SyncObjects.LastOrDefault();
}
}
}
UserControl xaml.cs
public partial class IndividualSync : UserControl
{
private IndividualSyncViewModel _vm;
public IndividualSync()
{
InitializeComponent();
_vm = new IndividualSyncViewModel(Event.EventInstance.EventAggregator);
rootGrid.DataContext = _vm;
}
#region properties
public SyncCriteriaItem SyncCriteriaItem
{
get { return (SyncCriteriaItem)GetValue(SyncCriteriaItemProperty); }
set { SetValue(SyncCriteriaItemProperty, value); }
}
public static readonly DependencyProperty SyncCriteriaItemProperty = DependencyProperty.Register("SyncCriteriaItem", typeof(SyncCriteriaItem), typeof(IndividualSync), new PropertyMetadata(null, OnCriteriaItemSet));
public GUISyncObject SelectedSYNC
{
get { return (GUISyncObject)GetValue(SelectedSYNCDependencyProperty); }
set { SetValue(SelectedSYNCDependencyProperty, value); }
}
public static readonly DependencyProperty SelectedSYNCDependencyProperty = DependencyProperty.Register("SelectedSYNC", typeof(GUISyncObject), typeof(IndividualSync), new PropertyMetadata(null, OnGUISyncItemSelect));
private static void OnCriteriaItemSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((IndividualSync)d)._vm.CriteriaItem = e.NewValue as SyncCriteriaItem;
}
private static void OnSyncItemSelect(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((IndividualSync)d)._vm.SelectedSyncObject = e.NewValue as GUISyncObject;
}
#endregion
}
Iam creating two dependency properties on the usercontrol which will be set from parent usercontrol.
Parent usercontrol Xaml
<UserControl.Resources>
<Converters:IndexToTabNameConverter x:Key="TabIndexConverter"/>
<DataTemplate x:Key="SyncTabItemTemplate" >
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Converter={StaticResource TabIndexConverter},
RelativeSource={RelativeSource AncestorType={x:Type telerik:RadTabItem}}}"/>
<Button Margin="10,0,0,0"
Style="{StaticResource CloseButton}"
ToolTipService.ToolTip="Save and Close">
<Button.Content>
<Path Data="M0,0 L6,6 M6, 0 L0,6"
SnapsToDevicePixels="True"
Stroke="Black"
StrokeThickness="1" />
</Button.Content>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<uxt:EventToCommand CommandParameter="{Binding}" Command="{Binding Path=DataContext.RemoveSyncCriteriaCommand,ElementName=rootGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="EmptyTemplate">
<TextBlock FontWeight="Bold" FontFamily="Comic Sans" FontStyle="Italic" Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="HeaderTemplate">
<Label FontWeight="Bold" HorizontalContentAlignment="Center" Content="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="SyncCriteriaTemplate">
<local:IndividualSync SyncCriteriaItem="{Binding}" SelectedGUISYNC="{Binding Path=DataContext.SelectedGUISYNCOBJECT}"/>
</DataTemplate>
<DataTemplate x:Key="newTabButtonHeaderTemplate">
<telerik:RadButton Style="{DynamicResource IconButtonStyle}" Width="20" Height="20" Command="{Binding Path=DataContext.NewCommand,ElementName=rootGrid}" ToolTip="Click to add new item">
<uxt:UxtXamlImage Template="{DynamicResource Com_Add}" />
</telerik:RadButton>
</DataTemplate>
<DataTemplate x:Key="itemContentTemplate">
<Grid/>
</DataTemplate>
<Selectors:TabItemTemplateSelector x:Key="headerTemplateSelector"
NewButtonTemplate="{StaticResource newTabButtonHeaderTemplate}"
ItemTemplate="{StaticResource SyncTabItemTemplate}"/>
<Selectors:TabItemTemplateSelector x:Key="contentTemplateSelector"
NewButtonTemplate="{StaticResource itemContentTemplate}"
ItemTemplate="{StaticResource SyncCriteriaTemplate}"/>
<Selectors:SyncTemplateSelector x:Key="syncTemplates">
<Selectors:SyncTemplateSelector.ProcessSyncTemplate>
<DataTemplate>
<telerik:Label Content="{Binding ProcessSync,StringFormat={}{0}}" BorderThickness="1" HorizontalContentAlignment="Left" FontStyle="Oblique" Width="{Binding Width,RelativeSource={RelativeSource AncestorType=telerik:RadListBox}}"/>
</DataTemplate>
</Selectors:SyncTemplateSelector.ProcessSyncTemplate>
<Selectors:SyncTemplateSelector.GUISyncTemplate>
<DataTemplate>
<Expander Header="GUISYNC" HeaderTemplate="{StaticResource HeaderTemplate}" Width="{Binding Width,RelativeSource={RelativeSource AncestorType=telerik:RadListBox}}" IsExpanded="{Binding Mode=TwoWay, Path=IsSelected, RelativeSource={RelativeSource AncestorType=telerik:RadListBoxItem, Mode=FindAncestor}}">
<telerik:RadTabControl ItemsSource="{Binding SyncCriteriaItem}" OverflowMode="Scroll" DropDownDisplayMode="Visible" SelectedItem="{Binding SelectedSyncCriteria, Mode=TwoWay}" SelectedIndex="0"
ItemTemplateSelector="{StaticResource headerTemplateSelector}"
ContentTemplateSelector="{StaticResource contentTemplateSelector}">
</telerik:RadTabControl>
</Expander>
</DataTemplate>
</Selectors:SyncTemplateSelector.GUISyncTemplate>
</Selectors:SyncTemplateSelector>
</UserControl.Resources>
<Grid Name="rootGrid">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<telerik:RadListBox Margin="20,5,20,0" Grid.Row="0" Grid.Column="0" Name="synclist" ItemsSource="{Binding CurrentSyncObjectCollection.SyncObjects}" ItemTemplateSelector="{StaticResource syncTemplates}" SelectedItem="{Binding SelectedSyncObject,Mode=TwoWay}" >
</telerik:RadListBox>
<telerik:RadComboBox Grid.Row="1" Grid.Column="0" Name="SyncOption" EmptyText="Select a sync type to be added " Margin="0,8,0,5" EmptySelectionBoxTemplate="{StaticResource EmptyTemplate}" ItemsSource="{Binding SyncTypes}" Width="{Binding ActualWidth, ElementName=synclist}" HorizontalContentAlignment="Center" Height="25" SelectedIndex="{Binding SelectedSyncIndex,Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<uxt:EventToCommand Command="{Binding AddSelectedSyncClick}" CommandParameter="{Binding Path=SelectedValue,ElementName=SyncOption}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</telerik:RadComboBox>
<telerik:RadButton Style="{DynamicResource IconButtonStyle}" Grid.Column="1" Grid.Row="0" Width="20" Height="20" VerticalAlignment="Top" IsEnabled="{Binding Path=SelectedSyncObject,Converter={uxt:ObjectToBoolConverter}}" ToolTip="Click to delete selected Sync"
Command="{Binding DeleteSyncObjectClick}">
<uxt:UxtXamlImage Template="{DynamicResource Com_Delete}" />
</telerik:RadButton>
</Grid>
I hope this will help. I have debugged the code and I observed even two Items are binded for CriteriaItems Breakpoint hitting for dependency property criteria item 3 times and one time null is coming.
Even when I change Tab selection Dependency Property setting code is hitting 2 times instead of one time.first time null is setting for CriteriaItem and second time respective item is coming.

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;
}
}

Button binding to Validation

So I have a 'Save' button that I want disabled if there are any validation errors and Enable if the errors are gone. I have a BindingGroup and was wondering if I could bind the IsEnabled to the BindingGroup Validation.HasError (but the Boolean inverse).
Here is the setup I have for the xaml
<telerik:RadWindow x:Class="Outreach_Application_1.MVVM.ContactPopup"
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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
Header="Contact Details"
mc:Ignorable="d" Style="{DynamicResource ContactWindowDictionary}">
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup Name="ContactGroup" />
</StackPanel.BindingGroup>
<Border Width="Auto" Height="25" Background="{StaticResource DhpBlueBrush}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="White" FontWeight="Bold" FontSize="12"
Text="{Binding ContactWindowHeader}"/>
</Border>
<DockPanel>
<StackPanel Margin="4">
<Label Content="Title" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="First" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="MI" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Last" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Position" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Email" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Phone" FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Phone Ext." FontWeight="Bold" HorizontalContentAlignment="Right"/>
<Label Content="Fax" FontWeight="Bold" HorizontalContentAlignment="Right"/>
</StackPanel>
<StackPanel Margin="4" Width="400">
<!-- Title -->
<ComboBox Width="50" Margin="2" HorizontalAlignment="Left"
SelectedValue="{Binding Path=Title,
Mode=TwoWay}"
ItemsSource="{Binding Path=TitleSource,
Mode=TwoWay}" />
<!-- First Name -->
<telerik:RadMaskedTextInput Margin="2" Width="125"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
TextMode="PlainText"
AllowInvalidValues="True"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=FirstName,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Middle Initial -->
<telerik:RadMaskedTextInput Margin="2" Width="30"
AllowInvalidValues="True"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
TextMode="PlainText"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=MiddleInitial,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Last Name -->
<telerik:RadMaskedTextInput Margin="2" Width="125"
AllowInvalidValues="True"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
TextMode="PlainText"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=LastName,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Position -->
<telerik:RadMaskedTextInput Margin="2" Width="125"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=Position,
Mode=TwoWay}" />
<!-- Email -->
<telerik:RadMaskedTextInput Margin="2" Width="125"
AllowInvalidValues="True"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
TextMode="PlainText"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=Email,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Phone Number -->
<telerik:RadMaskedTextInput Margin="2" Width="100"
AllowInvalidValues="True"
IsClearButtonVisible="False"
Mask="(ddd) ddd-dddd"
SelectionOnFocus="Unchanged"
TextMode="PlainText"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=PhoneNumber,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Phone Extension -->
<telerik:RadMaskedTextInput Margin="2" Width="50"
IsClearButtonVisible="False"
Mask=""
SelectionOnFocus="Unchanged"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=PhoneExtension,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
<!-- Fax Number -->
<telerik:RadMaskedTextInput Margin="2" Width="100"
AllowInvalidValues="True"
IsClearButtonVisible="False"
Mask="(ddd) ddd-dddd"
SelectionOnFocus="Unchanged"
TextMode="PlainText"
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}"
Value="{Binding Path=FaxNumber,
Mode=TwoWay,
ValidatesOnDataErrors=True,
BindingGroupName=ContactGroup,
UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DockPanel>
<Border BorderBrush="Black" BorderThickness="0,0,0,1" Margin="0 6" />
<DockPanel HorizontalAlignment="Center">
<Button Content="Cancel" Width="50" FontWeight="Bold" Margin="16 4"
Click="CancelContact_OnClick"/>
<Button Content="Save" Width="50" FontWeight="Bold" Margin="16 4"
Click="SaveContact_OnClick"
IsEnabled="{Binding BindingGroupName=ContactGroup, Path=(Validation.Errors)}">
</Button>
</DockPanel>
</StackPanel>
</telerik:RadWindow>
UPDATE: Here is the ViewModel side I am working with
public class Contact : ViewModelBase, IDataErrorInfo, INotifyPropertyChanged
{
private string _email;
private string _faxNumber;
private string _firstName;
private string _lastName;
private string _middleInitial;
private string _phoneExtension;
private string _phoneNumber;
private string _position;
private string _error = string.Empty;
//Declare contact variables
private string _title;
private bool _canSave;
//Title
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
//First Name
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
//MiddleInitial
public string MiddleInitial
{
get { return _middleInitial; }
set
{
_middleInitial = value;
OnPropertyChanged("MiddleInitial");
}
}
//Last Name
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
//Position
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
//Email
public string Email
{
get { return _email; }
set
{
_email = value;
OnPropertyChanged("Email");
}
}
//Phone Number
public string PhoneNumber
{
get { return _phoneNumber; }
set
{
_phoneNumber = value;
OnPropertyChanged("PhoneNumber");
}
}
//Phone Extension
public string PhoneExtension
{
get { return _phoneExtension; }
set
{
_phoneExtension = value;
OnPropertyChanged("PhoneExtension");
}
}
//Fax Number
public string FaxNumber
{
get { return _faxNumber; }
set
{
_faxNumber = value;
OnPropertyChanged("FaxNumber");
}
}
public bool CanSave
{
get { return this._canSave; }
set
{
this._canSave = value;
base.OnPropertyChanged("CanSave");
}
}
#region IDataErrorInfo Members
public string Error
{
get { return _error; }
}
public string this[string columnName]
{
get
{
_error = string.Empty;
string pattern;
//First name Validation
pattern = #"\b^[A-Za-z][a-zA-Z '&-]*[A-Za-z]*$\b";
if (columnName == "FirstName" && Regex.IsMatch(FirstName, pattern) == false)
{
_error = "Invalid first name.";
CanSave = false;
}
if (columnName == "FirstName" && string.IsNullOrWhiteSpace(FirstName))
{
_error = "First name is required.";
CanSave = false;
}
//Middle Initial Validation
pattern = #"(^$)|\b^[A-Za-z]$\b";
if (columnName == "MiddleInitial" && Regex.IsMatch(MiddleInitial, pattern) == false)
{
_error = "Invalid middle initial.";
CanSave = false;
}
//Last name validation
pattern = #"\b^[A-Za-z][a-zA-Z '&-]*[A-Za-z]*$\b";
if (columnName == "LastName" && Regex.IsMatch(LastName, pattern) == false)
{
_error = "Invalid last name.";
CanSave = false;
}
if (columnName == "LastName" && string.IsNullOrWhiteSpace(LastName))
{
_error = "Last name is required.";
CanSave = false;
}
//Email validation
pattern =
#"^(?("")("".+?(?<!\\)""#)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9A-Za-z])#))" +
#"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]*\.)+[A-Za-z0-9][\-A-Za-z0-9]{0,22}[A-Za-z0-9]))$";
if (columnName == "Email" && Regex.IsMatch(Email, pattern) == false)
{
_error = #"Please enter a valid email address.";
CanSave = false;
}
if (columnName == "Email" && string.IsNullOrWhiteSpace(Email))
{
_error = #"Email address is required.";
CanSave = false;
}
//Phone number validation
pattern = #"^[0-9]{10}$";
if (columnName == "PhoneNumber" && Regex.IsMatch(PhoneNumber, pattern) == false)
{
_error = "Please enter a 10 digit phone number.";
CanSave = false;
}
if (columnName == "PhoneNumber" && string.IsNullOrWhiteSpace(PhoneNumber))
{
_error = "A phone number is required.";
CanSave = false;
}
//Phone extension validation
pattern = #"^(^$)|[0-9]+$";
if (columnName == "PhoneExtension" && Regex.IsMatch(PhoneExtension, pattern) == false)
{
_error = "Only numerical digits are allowed.";
CanSave = false;
}
//Fax number validation
pattern = #"^[0-9]{10}$";
if (columnName == "FaxNumber" && Regex.IsMatch(FaxNumber, pattern) == false)
{
_error = "Please enter a 10 digit fax number.";
CanSave = false;
}
if (columnName == "FaxNumber" && string.IsNullOrWhiteSpace(FaxNumber))
{
_error = "A fax number is required.";
CanSave = false;
}
return _error;
}
}
#endregion
}
I attempted to bind the save button to CanSave but as soon as one object is valid it enables the button. I want to compliment to that action. As soon as one object is invalid the button is disabled.
Its quite simple actually,
XAML
<Button Content="Save" Width="50" FontWeight="Bold" Margin="16 4"
Click="SaveContact_OnClick"
IsEnabled="{Binding Path=IsValid}">
Model.cs
...
...
//Below Code to check if the model is Valid (without errors)
public bool IsValid
{
get
{
return (Validation.Errors.Count == 0);
}
}
//Also you might need to add the below line in your property changed
protected virtual void OnPropertyChanged(string propertyName)
{
...
//Below line is needed to fire Notify Property changed
this.PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));
...
}
...
...
Let me know if you have any doubts
bind the IsEnabled to the BindingGroup Validation.HasError (but the Boolean inverse).
Binding Groups were designed more for a Submit / pull motif where a button is always active and starts the validation process. A failure will set the NotifyOnValidationError property to true, but that is after the fact and doesn't help in this situation.
What you seek is a push type validation where all validations could push any changes real time to a variable, which could be bound to by the IsEnabled of the button.
See Validation in Windows Presentation Foundation - CodeProject for an example.

Changes to Observable Collection not updating DataGrid

I have an observable collection in my viewmodel as follows..Not perfect but just code that looks as below populating an observable collection and bind it to the datagrid. I also have two buttons beside the datagrid one for upcommand and one for downcommand. When I click the upbutton everything works fine in which the selected row moves up and the sequence order is getting updated, Observable Collection is also getting updated. But the changes are not being bound to the DataGrid. Similarly for the downcommand. My view is below. The commands are working just fine only the view is not being updated. .Please help.
Issue is with observable collection, move up and move down.
ViewModel:
public class JobConfigurationViewModel : BindableBase
{
public JobConfigurationLogic JobConfigurationLogic =
new JobConfigurationLogic(new JobConfigurationResultsRepository());
public SrcDestConfigurationLogic SrcDestConfigurationLogic =
new SrcDestConfigurationLogic(new SrcDestCofigurationRepository());
private string _enterprise;
public string Enterprise
{
get { return _enterprise; }
set { SetProperty(ref _enterprise, value); }
}
private int currentJobID;
private int currentSequence;
private int previousJobID;
private int previousSequence;
private string _site;
public string Site
{
get { return _site; }
set { SetProperty(ref _site, value); }
}
private int _siteID;
public int SiteID
{
get { return _siteID; }
set { SetProperty(ref _siteID, value); }
}
private ObservableCollection<JobConfigurationResults> _jobEntities;
public ObservableCollection<JobConfigurationResults> JobEntities
{
get { return _jobEntities; }
set
{
SetProperty(ref _jobEntities, value);
//JobEntities.CollectionChanged += JobEntities_CollectionChanged;
OnPropertyChanged("JobEntities");
}
}
//void JobEntities_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
//{
// if (e.OldItems != null)
// {
// foreach (var item in e.OldItems)
// {
// this.GetJobConfigurationResults();
// }
// }
//}
//Source System List for Job
private List<SourceSiteSystem> _lstJobSrcSystems;
public List<SourceSiteSystem> LstJobSrcSystems
{
get { return _lstJobSrcSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobSrcSystems, value);
}
}
//Deestination System List for Job
private List<DestinationSiteSystem> _lstJobDestSystems;
public List<DestinationSiteSystem> LstJobDestSystems
{
get { return _lstJobDestSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobDestSystems, value);
}
}
//the Selected Source Site system ID
private int _selectedSrcSiteSystemId = 0;
public int SelectedSrcSiteSystemId
{
get { return _selectedSrcSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedSrcSiteSystemId, value);
}
}
//the Selected Source Site system from the dropdown
private SourceSiteSystem _selectedSrcSiteSystem;
public SourceSiteSystem SelectedSrcSiteSystem
{
get { return _selectedSrcSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedSrcSiteSystem, value);
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
}
}
}
//the Selected Destination Site system ID
private int _selectedDestSiteSystemId = 0;
public int SelectedDestSiteSystemId
{
get { return _selectedDestSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedDestSiteSystemId, value);
}
}
//the Selected Destination Site system from the dropdown
private DestinationSiteSystem _selectedDestSiteSystem;
public DestinationSiteSystem SelectedDestSiteSystem
{
get { return _selectedDestSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedDestSiteSystem, value);
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
}
}
}
private JobConfigurationResults _jeJobConfigurationResults;
public JobConfigurationResults JEJobConfigurationResults
{
get { return _jeJobConfigurationResults; }
set { _jeJobConfigurationResults = value; }
}
private List<JobTaskConfiguration> _taskSelectionList = new List<JobTaskConfiguration>();
private CancellationTokenSource _source;
private RelayCommand<object> _commandSaveInstance;
private RelayCommand<object> _hyperlinkInstance;
private RelayCommand<object> _commandRunJob;
private RelayCommand<object> _upCommand;
private RelayCommand<object> _downCommand;
private IEventAggregator _aggregator;
/// <summary>
/// This is a Subscriber to the Event published by EnterpriseViewModel
/// </summary>
/// <param name="agg"></param>
public JobConfigurationViewModel(IEventAggregator agg)
{
_aggregator = agg;
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
//evt.Unsubscribe();
StartPopulate();
}
private async void StartPopulate()
{
await TaskPopulate();
}
//This is to ensure that the publisher has published the data that is needed for display in this workspace
private bool TaskProc()
{
Thread.Sleep(500);
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
return DoPopulate();
}
private Task<bool> TaskPopulate()
{
_source = new CancellationTokenSource();
return Task.Factory.StartNew<bool>(TaskProc, _source.Token);
}
/// <summary>
/// This method handles the populating of the Source and Destination Dropdowns and the Job entity and Task Datagrid
/// This is mainly driven by the Site selected in the previous workspace
/// </summary>
/// <returns></returns>
private bool DoPopulate()
{
PopulateSourceDestinations(this.SiteID);
return true;
}
/// <summary>
/// this method displays all entities and tasks for the site.
/// This is done async so that the Publisher thread is not held up
/// </summary>
public void GetJobConfigurationResults()
{
if (SelectedSrcSiteSystem == null)
{
SelectedSrcSiteSystem = LstJobSrcSystems[0];
}
if (SelectedDestSiteSystem == null)
{
SelectedDestSiteSystem = LstJobDestSystems[0];
}
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
var jobConfigurationResults = new JobConfigurationResults
{
SourceId = SelectedSrcSiteSystemId,
DestinationId = SelectedDestSiteSystemId
};
JobEntities = new ObservableCollection<JobConfigurationResults>();
JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId,
jobConfigurationResults.DestinationId);
_taskSelectionList = new List<JobTaskConfiguration>(JobEntities.Count * 3);
}
/// <summary>
/// //Adding a method to pupulate the Source and Destination dropdown lists.
/// This is done async so that the Publisher thread is not held up
/// </summary>
///
///
public async void PopulateSourceDestinations(int siteId)
{
this.LstJobSrcSystems = SrcDestConfigurationLogic.LoadSourceSiteSystems(siteId);
this.LstJobDestSystems = SrcDestConfigurationLogic.LoadDestinationSystems(siteId);
GetJobConfigurationResults();
}
public ICommand HyperlinkCommand
{
get
{
if (_hyperlinkInstance == null)
_hyperlinkInstance = new RelayCommand<object>(openDialog);
return _hyperlinkInstance;
}
}
private void openDialog(object obj)
{
JobConfigurationResults results = obj as JobConfigurationResults;
JEJobConfigurationResults = JobEntities.SingleOrDefault(x => x.JobEntityId == results.JobEntityId);
}
public ICommand CommandSave
{
get
{
if (_commandSaveInstance == null)
_commandSaveInstance = new RelayCommand<object>(saveJobConfigurationChanges);
return _commandSaveInstance;
}
}
public ICommand CommandRunJob
{
get { return _commandRunJob ?? (_commandRunJob = new RelayCommand<object>(RunJob)); }
}
/// <summary>
/// this saves all the changes in the selection made by the user
/// </summary>
/// <param name="ob"></param>
public void saveJobConfigurationChanges(object ob)
{
foreach (var job in JobEntities)
{
JobConfigurationLogic.UpdateSequence(job.SequenceOrder, job.JobEntityId); //Save Sequence Changes
int jobEntityId = job.JobEntityId;
foreach (var task in job.TaskDetails)
{
int id = task.JobTask_ID;
bool isSelected = task.IsSelected;
_taskSelectionList.Add(task);
}
}
JobConfigurationLogic.UpdateTaskSelection(_taskSelectionList);
}
public ICommand UpCommand
{
get
{
if (_upCommand == null)
_upCommand = new RelayCommand<object>(MoveUp);
return _upCommand;
}
}
private void MoveUp(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder - 1;
JobConfigurationResults previousResult =
JobEntities.FirstOrDefault(n => n.SequenceOrder == currentSequence);
try
{
previousJobID = previousResult.JobId;
previousSequence = previousResult.SequenceOrder;
int newindex = JobEntities.IndexOf(results);
int oldindex = JobEntities.IndexOf(previousResult);
JobEntities[newindex].SequenceOrder = previousResult.SequenceOrder;
JobEntities[oldindex].SequenceOrder = results.SequenceOrder + 1;
//foreach (var i in JobEntities)
//{
// MessageBox.Show(i.EntityName + " " + i.SequenceOrder);
//}
}
catch (NullReferenceException)
{
MessageBox.Show("You have reached the top");
}
}
else
{
MessageBox.Show("Select a row first");
}
}
public ICommand DownCommand
{
get
{
if (_downCommand == null)
_downCommand = new RelayCommand<object>(MoveDown);
return _downCommand;
}
}
private void MoveDown(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder + 1;
JobConfigurationResults previousResult =
JobEntities.FirstOrDefault(n => n.SequenceOrder == currentSequence);
try
{
previousJobID = previousResult.JobId;
previousSequence = previousResult.SequenceOrder;
int newindex = JobEntities.IndexOf(results);
int oldindex = JobEntities.IndexOf(previousResult);
JobEntities[newindex].SequenceOrder = previousResult.SequenceOrder;
JobEntities[oldindex].SequenceOrder = results.SequenceOrder - 1;
foreach (var i in JobEntities)
{
MessageBox.Show(i.EntityName + " " + i.SequenceOrder);
}
}
catch (NullReferenceException)
{
MessageBox.Show("You cannot move further down");
}
}
else
{
MessageBox.Show("Select a row first");
}
}
/// <summary>
/// Execute an etl job using the current job id
/// </summary>
private void RunJob(object obj)
{
JobEngine jobEngine = new JobEngine();
var jobId = JobEntities[0].JobId;
jobEngine.ProcessJob(jobId);
}
}
View.XAML:
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate1">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate2">
<StackPanel>
<TextBlock Text="{Binding Status}"/>
</StackPanel>
</DataTemplate>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="Bisque" />
<Setter Property="TextBlock.FontSize" Value="14" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="0.14*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.ColumnSpan="2">
<TextBlock>
<Label FontSize="16">Enterprise:</Label>
<Label Name="lblEnterprise" FontSize="16" Content="{Binding Enterprise}" />
<Label FontSize="16">Site:</Label>
<Label Name="lblSite" FontSize="16" Content="{Binding Site}" HorizontalAlignment="Right" />
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock>
<Label FontSize="16">Source System:</Label>
<ComboBox Name="CmbSource" Margin="3" SelectedIndex="0" Width="220" ItemsSource="{Binding LstJobSrcSystems}" SelectedItem="{Binding SelectedSrcSiteSystem, Mode=TwoWay}" DisplayMemberPath ="SourceSystemType"></ComboBox>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="1">
<TextBlock HorizontalAlignment="Right" Margin="2,2,14,2">
<Label FontSize="16">Destination System:</Label>
<ComboBox Name="CmbDestination" SelectedIndex="0" Margin="2" Width="220" ItemsSource="{Binding LstJobDestSystems}" SelectedItem="{Binding SelectedDestSiteSystem, Mode=TwoWay}" DisplayMemberPath ="DestinationSystemType"></ComboBox>
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="2" Grid.ColumnSpan="2" Margin="0,2,0,46" Grid.RowSpan="3">
<Border BorderThickness="1" Width="{Binding ElementName=dgEntities,Path=Width}" Margin="35,48,60,48" Height="{Binding ElementName=grd,Path=Height}">
<Grid Name="grd" Height="30" Width="{Binding ElementName=JobConfigCol,Path=Width}" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="c1" Width="82*"/>
<ColumnDefinition x:Name="c2" Width="83*"/>
<ColumnDefinition x:Name="c3" Width="83*"/>
<ColumnDefinition x:Name="c4" Width="96*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="24,0,78,0" Width="62" Content="Extract"/>
<Label Grid.Column="1" HorizontalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="40,0,38,0" Width="88" Content="Transform"/>
<Label Grid.Column="2" HorizontalAlignment="Center" FontWeight="Bold" FontSize="16" Margin="66,0,54,0" Width="46" Content="Load"/>
</Grid>
</Border>
<DataGrid x:Name="dgEntities" Width="690" Height="370" Margin="40,-48,65,-8" CanUserAddRows="false" SelectionMode="Single" ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserSortColumns="False">
<DataGrid.DataContext>
<CollectionViewSource x:Name="CollectionViewSource" Source="{Binding Path=JobEntities,Mode=TwoWay,NotifyOnTargetUpdated=True}">
</CollectionViewSource>
</DataGrid.DataContext>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Status" Width="138" MaxWidth="138">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Margin="5" Content="{Binding TaskDetails[0].Status,Mode=TwoWay,NotifyOnTargetUpdated=True}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.HyperlinkCommand}" CommandParameter="{Binding ElementName=dgEntities,Path=SelectedItem}" Foreground="Blue" Cursor="Hand" MouseDoubleClick="Control_OnMouseDoubleClick" >
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBlock TextDecorations="Underline">
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn MinWidth="{Binding ElementName=c1,Path=ActualWidth}">
<DataGridTemplateColumn.Header>
<CheckBox Name="chkHeaderExtract" Checked="ChkHeaderExtract_OnChecked" Unchecked="ChkHeaderExtract_OnUnchecked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" Name="chkExtract" IsChecked="{Binding TaskDetails[0].IsSelected,Mode=TwoWay,NotifyOnTargetUpdated=True,UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Status" Width="138" MaxWidth="138">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Margin="5" Content="{Binding TaskDetails[1].Status,Mode=TwoWay,NotifyOnTargetUpdated=True,UpdateSourceTrigger=PropertyChanged}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.HyperlinkCommand}" CommandParameter="{Binding ElementName=dgEntities,Path=SelectedItem}" Cursor="Hand" Foreground="Blue" MouseDoubleClick="Control_OnMouseDoubleClick_2" >
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBlock TextDecorations="Underline">
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn MinWidth="{Binding ElementName=c2,Path=ActualWidth}">
<DataGridTemplateColumn.Header>
<CheckBox Name="chkHeaderTransform" Checked="ChkHeaderTransform_OnChecked" Unchecked="ChkHeaderTransform_OnUnchecked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" Name="chkTransform" IsChecked="{Binding TaskDetails[1].IsSelected,Mode=TwoWay,NotifyOnTargetUpdated=True,UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Status" Width="138" MaxWidth="138">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Margin="5" Content="{Binding TaskDetails[2].Status,Mode=TwoWay,NotifyOnTargetUpdated=True}" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.HyperlinkCommand}" CommandParameter="{Binding ElementName=dgEntities,Path=SelectedItem}" Cursor="Hand" Foreground="Blue" MouseDoubleClick="Control_OnMouseDoubleClick" >
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBlock TextDecorations="Underline">
<ContentPresenter />
</TextBlock>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn MinWidth="{Binding ElementName=c3,Path=ActualWidth}">
<DataGridTemplateColumn.Header>
<CheckBox Name="chkHeaderLoad" Checked="ChkHeaderLoad_OnChecked" Unchecked="ChkHeaderLoad_OnUnchecked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center" Name="chkLoad" IsChecked="{Binding TaskDetails[2].IsSelected,Mode=TwoWay,NotifyOnTargetUpdated=True,UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding EntityName}" Header="Entity" Width="{Binding ElementName=c4,Path=ActualWidth}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel HorizontalAlignment="Right" Margin="0,-388,43,0" Height="108">
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Top"><InlineUIContainer>
<Button Height="40" Width="40" x:Name="btnup" Command="{Binding UpCommand}" CommandParameter="{Binding ElementName=dgEntities,Path=SelectedItem}" Click="Btnup_OnClick">
<Image x:Name="Up" Source="../Images/up.jpg" Height="40" />
</Button>
</InlineUIContainer></TextBlock>
<TextBlock HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,2"><InlineUIContainer>
<Button Height="40" Width="40" x:Name="btndown" Command="{Binding DownCommand}" CommandParameter="{Binding ElementName=dgEntities,Path=SelectedItem}" Click="btndown_Click">
<Image x:Name="Down" Source="../Images/Down.jpg" Height="40" />
</Button>
</InlineUIContainer></TextBlock>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="4" Grid.Column="0">
<TextBlock Name="tbldemo" HorizontalAlignment="Right">
<Button Name="btnRun" Height="30" Width="90" FontSize="18" Margin="9" Command="{Binding CommandRunJob}" >Run</Button>
<Button Name="btnSave" Height="30" Width="90" FontSize="18" Margin="9" Command="{Binding CommandSave, Mode=OneWay}">Save</Button>
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="4" Grid.Column="0">
<TextBlock HorizontalAlignment="Left">
<Button Name="btnclose" Height="30" Width="90" FontSize="18" Margin="19,9">Close</Button>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>
CS: Im saying CollectionViewSource.View.Refresh();
I believe you are on the right track. the reason your view is not updating when you are changing properties in the collection from the view model, is because while you are making use of ObservableCollection for it to work correctly in the way you are describing, you will need to make sure that the type that you are binding to in the collection implements INotifyProperyChanged. Once you do this, the ObservableCollection will receive this message and in turn notify your view of the changes and ultimately causing a view update.
so in your case you would need to make sure that the type JobConfigurationResults implements the INotifyPropertyChanged interface.
For a reference, you can view this link and towards the bottom of the page you will find the note describing what I am suggesting. http://msdn.microsoft.com/en-us/library/ms748365(v=vs.110).aspx

Binding not triggering Action<>

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

Resources