ListView Popup key functionality - wpf

I have implemented a listview that appears as a popup list. Now I would like to add key functionalities to it, like if whenever up arrow is pressed in a text box it should select an item in my list view and if pressing of KEY_UP/DOWN is continued it should continue changing its index respectively.
This is the EditMessageTextBox and associated EditMessageTagPopup
This is the XAML code used:
<Grid x:Name="EditGrid"
Grid.Row="1"
Visibility="{Binding EditMessageControlVisibility}"
FocusManager.IsFocusScope="False"
VerticalAlignment="Center"
Grid.Column="1"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="EditMessageBorder"
Grid.Row="0"
BorderThickness="1"
CornerRadius="1"
Margin="0,10,0,0"
BorderBrush="Gray">
<Grid>
<TextBlock FontSize="16"
Margin="10,0,0,3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="Edit message"
Foreground="{StaticResource brushWatermarkForeground}"
Visibility="{Binding ElementName=EditMessageTextBox, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox Name="EditMessageTextBox"
Text="{Binding MessageToEdit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="Transparent"
BorderThickness="0"
Foreground="Black"
FontSize="16"
Margin="8,1,1,1"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
MinHeight="35"
ScrollViewer.VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
AcceptsReturn="False"
KeyUp="OnEditMessage_KeyUp"
SpellCheck.IsEnabled="true" />
</Grid>
</Border>
<StackPanel Grid.Row="1"
Margin="0,10"
Orientation="Horizontal">
<Button Background="Transparent"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Foreground="Black"
BorderBrush="Gray"
BorderThickness="0.8"
Width="100"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
Click="EditMessageCancelButton_Clicked">Cancel</Button>
<Button Name="EditMessageButton"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Background="#007a5a"
Foreground="White"
BorderBrush="#007a5a"
Margin="15,0,0,0"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
BorderThickness="0.8"
IsEnabled="True"
Width="140"
Content="Save Changes"
Click="EditMessageSaveButton_Clicked" />
</StackPanel>
<Popup x:Name="EditMessageTagPopup"
AllowsTransparency="True"
IsOpen="{Binding IsOpenTagPopUp}"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<Border materialDesign:ShadowAssist.ShadowDepth="Depth5"
CornerRadius="5"
Background="White"
BorderBrush="Black"
BorderThickness="0.8"
MaxHeight="200">
<ListView x:Name="EditTaggedUsers"
Focusable="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource UserListForTag}}"
SelectionChanged="EditMessageTagList_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="_Border"
Padding="8">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF3BD38E" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
<Trigger Property="IsSelected"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF205B4B" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="-15,0,0,0"
Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0"
RadiusY="5"
RadiusX="5"
Height="20"
Width="20">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding ProfileImage}"
Stretch="UniformToFill" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Grid.Column="1"
Text="{Binding FullName}"
Margin="-10,0,0,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Grid>
and here is code behind:
ApplicationContext.StoredEditingMessage = (String)ApplicationContext.EditMessageText;
var messageData = ((TextBox)sender).DataContext as ChatsModel;
var EditMessagePopup = FindEditMessagePopup(MessageList);
Border EditEessageBorder = EditMessagePopup.Child as Border;
ListView EditMessageTagList = EditEessageBorder.Child as ListView;
Dispatcher?.Invoke(() =>
{
if (_contactsViewModel.GroupedChatByDate
.Find(x => messageData != null && x.MessageGuid == messageData.MessageGuid)
.IsOpenTagPopUp == false) return;
var index = _contactsViewModel.UsersListForTag.IndexOf(_contactsViewModel.UsersListForTag.FirstOrDefault(x => x.Selected == true));
switch (e.Key)
{
case Key.Up:
if (EditMessageTagList.SelectedIndex > 0)
{
EditMessageTagList.SelectedIndex -= 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex = _contactsViewModel.UsersListForTag.Count - 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
case Key.Down:
if (EditMessageTagList.SelectedIndex + 1 == _contactsViewModel.UsersListForTag.Count)
{
EditMessageTagList.SelectedIndex = 0;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex += 1;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
}
_contactsViewModel.UsersListForTag.ForEach(x => x.Selected = false);
if (index != -1)
{
_contactsViewModel.UsersListForTag[index].Selected = true;
}
});
I have tried adding an item in scroll into view() instead of selected index yet no update
when there is a perfect selection made this function is invoked from code behind
private void EditMessageTagList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var messageModel = ((ListView)sender).DataContext as ChatsModel;
if (((ListView)sender).SelectedItem is UserModel selectedUserForTag)
{
// _contactsViewModel.GroupedChatByDate.Find(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid) .IsOpenTagPopUp = false;
string SelectedTag = (selectedUserForTag.Id == ApplicationContext.CurrentLoggedInUserGuid) ? $"{selectedUserForTag.UserName.Replace("(you) ", "")} " : $"{selectedUserForTag.UserName} ";
_contactsViewModel.GroupedChatByDate.Find
(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid)
.MessageToEdit = "#" + SelectedTag;
}
// ((ListView) sender).SelectedItem = null;
}
catch (Exception exception)
{
LoggingManager.Error(exception);
}
}
Here is screen recording regarding issue
and
Here is working functionality

The problem is that after each navigation to the next item of the ListView you want to set focus to the selection TextBox which binds to the SelectedItem. Otherwise navigating the items of a ListView with the help of the arrow keys is already the default behavior of the ListView.
The simplest solution is to capture the keyboard input using UIElement.InputBinding on the selection TextBox (which enables to handle the keys in the view model) and then
Select the next/previous item
Scroll the SelectedItem into view
Move the focus to the selection TextBox
Move the caret of the selection TextBox to the end
DataItem.cs
class DataItem
{
public string FullName { get; set; }
public DataItem(string fullName) => this FullName = fullName;
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<DataItem> DataItems { get; set; }
public ICommand SelectNextCommand => new AsyncRelayCommand(SelectNextItem);
public ICommand SelectPreviousCommand => new AsyncRelayCommand(SelectPreviousItem);
private bool IsSelectedItemChangeInternal { get; set; }
private DataItem selectedDataItem;
public DataItem SelectedDataItem
{
get => this.selectedDataItem;
set
{
this.selectedDataItem = value;
OnPropertyChanged();
// Do not filter the list when the selected item was set by the user
// e.g. by using arrow keys
if (!this.IsSelectedItemChangeInternal)
{
UpdateSearchFilter();
}
}
}
private string filterKey;
public string FilterKey
{
get => this.filterKey;
set
{
this.filterKey = value;
OnPropertyChanged();
// Only apply filters when the FilterKey was changed by the user
// e.g. by editing the edit TextBox that binds to this property
if (!this.IsSelectedItemChangeInternal)
{
ApplySearchFilter();
}
}
}
public ViewModel()
{
this.DataItems = new ObservableCollection<DataItems>();
for (var index = 0; index < 100; index++)
{
this.DataItems.Add(new DataItem("name " + index.ToString());
}
}
private void ApplySearchFilter()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Games);
this.IsSelectedItemChangeInternal = true;
collectionView.Filter = item =>
string.IsNullOrWhiteSpace(this.FilterKey) || (item as DetailItem).FullName.StartsWith(this.FilterKey);
// pre-select the first match
collectionView.MoveCurrentToFirst();
this.IsSelectedItemChangeInternal = false;
}
private void UpdateSearchFilter()
{
this.IsSelectedItemChangeInternal = true;
this.FilterKey = this.SelectedDataItem.FullName;
this.IsSelectedItemChangeInternal = false;
}
private void SelectNextItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToNext();
// Loop
if (collectionView.IsCurrentAfterLast)
{
collectionView.MoveCurrentToFirst();
}
}
private void SelectPreviousItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToPrevious();
// Loop
if (collectionView.IsCurrentBeforeFirst)
{
collectionView.MoveCurrentToLast();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
}
}
MainWindow.xaml.cs
private void AdjustFocus_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
listView.ScrollIntoView(listView.SelectedItem);
Application.Current.Dispatcher.InvokeAsync(() =>
{
Keyboard.Focus(this.EditMessageTextBox);
this.EditMessageTextBox.CaretIndex = this.EditMessageTextBox.Text.Length;
});
}
private void AdjustFocus_OnOpened(object sender, EventArgs e)
{
this.EditTaggedUsers.Focus();
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContex>
<Grid>
<TextBox x:Name="EditMessageTextBox"
Text="{Binding FilterKey}">
<TextBox.InputBindings>
<KeyBinding Key="Down"
Command="{Binding SelectNextCommand}" />
<KeyBinding Key="Up"
Command="{Binding SelectPreviousCommand}" />
</TextBox.InputBindings>
</TextBox>
<Popup IsOpen="True"
Opened="AdjustFocus_OnOpened"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<ListView IsSynchronizedWithCurrentItem="True"
Height="400"
SelectedItem="{Binding SelectedDataItem}"
ItemsSource="{Binding DataItems}"
SelectionChanged="AdjustFocus_OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type DataItem}">
<TextBox Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
</Grid>
</Window>
Remarks
As the navigation is done using the CollectionView of the ItemsSource the ListView.IsSynchronizedWithCurrentItem property must be set to true. Otherwise the navigation of the CollectionView won't effect the view.

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.

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

How to find visible elements inside of a ScrollViewer and WrapPanel

I've a WrapPanel surrounded with a ScrollViewer, I want to find the visible elements on the screen when I click a button.
My code like:
<ScrollViewer>
<WrapPanel>
<Label Width="500" Height="500"/>
<Label Width="500" Height="500"/>
<Label Width="500" Height="500"/>
....
....
....
<Label Width="500" Height="500"/>
<Label Width="500" Height="500"/>
<Label Width="500" Height="500"/>
</WrapPanel>
</ScrollPanel>
How can I find the visible Label elements when ScrollViewer scrolled to some offset.
Hopefully this will get you started (you may want to change 'svViewportBounds.IntersectsWith' with 'Contains'.
public partial class MainWindow : Window
{
public string VisibleItems
{
get { return (string)GetValue(VisibleItemsProperty); }
set { SetValue(VisibleItemsProperty, value); }
}
public static readonly DependencyProperty VisibleItemsProperty =
DependencyProperty.Register("VisibleItems", typeof(string), typeof(MainWindow), new PropertyMetadata("??"));
public List<string> Items { get; private set; }
public MainWindow()
{
Items = new List<string>();
for (int i = 0; i < 25; ++i )
{
Items.Add("item_" + i);
}
DataContext = this;
InitializeComponent();
}
void OnScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer sv = (ScrollViewer)sender;
var visibleItems = new List<int>();
Rect svViewportBounds = new Rect(sv.HorizontalOffset, sv.VerticalOffset, sv.ViewportWidth, sv.ViewportHeight);
for(int i = 0; i < Items.Count; ++i)
{
var container = itemsHost.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if(container != null)
{
var offset = VisualTreeHelper.GetOffset(container);
var bounds = new Rect(offset.X, offset.Y, container.ActualWidth, container.ActualHeight);
if (svViewportBounds.IntersectsWith(bounds))
{
visibleItems.Add(i);
}
}
}
VisibleItems = string.Join(", ", visibleItems.ToArray());
}
}
<Window x:Class="WpfApplication59.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
WindowStartupLocation="CenterScreen"
Width="400"
Height="400">
<DockPanel>
<Border BorderThickness="1"
BorderBrush="Black"
DockPanel.Dock="Top">
<TextBlock Text="{Binding VisibleItems}"
Margin="5" />
</Border>
<ItemsControl ItemsSource="{Binding Items}" Name="itemsHost">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
ScrollChanged="OnScrollViewerScrollChanged">
<WrapPanel IsItemsHost="True" />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1"
BorderBrush="Black"
Width="100"
Height="100">
<Label Content="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Margin"
Value="4" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DockPanel>

Content control binding not working

When i set the content of the content control like below but the binding of the element inside the content get break.
i have given a content inside a property of a class and set the property as the content to the content control.
[Xmal]
<Grid>
<Button HorizontalAlignment="Center"
VerticalAlignment="Top"
Click="Button_Click_1"
Content="Click" />
<local:MyTile x:Name="mytile">
<local:MyTile.TileViewContent>
<StackPanel>
<TextBox x:Name="text"
Background="Red"
Text="MyText" />
<TextBox Text="{Binding ElementName=text, Path=Text,Mode=TwoWay}" />
</StackPanel>
</local:MyTile.TileViewContent>
</local:MyTile>
<ContentControl x:Name="contentcontrol" />
</Grid>
[C#]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
contentcontrol.Content = mytile.TileViewContent;
}
}
public class MyTile:Control
{
public FrameworkElement TileViewContent
{
get { return (FrameworkElement)GetValue(TileViewContentProperty); }
set { SetValue(TileViewContentProperty, value); }
}
public static readonly DependencyProperty TileViewContentProperty =
DependencyProperty.RegisterAttached("TileViewContent", typeof(FrameworkElement), typeof(MyTile), new PropertyMetadata(null));
}
When i set the content the binding not working. please help
If you want to simply binding is works, not necessarily through ContentControl, use the Style for your element:
<Window.Resources>
<Style x:Key="MyTemplateForMyControl" TargetType="{x:Type local:MyTile}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyTile}">
<StackPanel>
<TextBox x:Name="MyTextBox" Text="MyText" Background="Red" />
<TextBox Text="{Binding ElementName=MyTextBox, Path=Text}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Click" Click="Button_Click_1" />
<local:MyTile x:Name="MyTile" />
</Grid>
In code we are set the Style for your Control:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
MyTile.Style = this.Resources["MyTemplateForMyControl"] as Style;
}
If it is necessary to use ContentControl I can recommend instead of you Control use a DataTemplate:
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyDataForTemplate}">
<StackPanel>
<TextBox x:Name="MyTextBox" Text="{Binding TextBoxContent}" Background="Red" />
<TextBox Text="{Binding ElementName=MyTextBox, Path=Text}" />
</StackPanel>
</DataTemplate>
<!-- Some data -->
<local:MyDataForTemplate x:Key="MyDataForTile" TextBoxContent="MyText" />
</Window.Resources>
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="Click" Click="Button_Click_1" />
<ContentControl Name="TileContentControl" />
</Grid>
There will be some data for a template:
public class MyDataForTemplate
{
string textBoxContent = "";
/// <summary>
/// Text for TextBox
/// </summary>
public string TextBoxContent
{
get
{
return textBoxContent;
}
set
{
textBoxContent = value;
}
}
}
In code we are set the data for a template:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
TileContentControl.Content = this.Resources["MyDataForTile"] as MyDataForTemplate;
}

refresh datagrid on view from viewmodel

I have a datagrid on a view that is bound to a viewmodel. When I initialze the view, the datagrid is populated with data from the viewmodel (ObservableCollection) as it should. However, with I try to do a search against the data, the datagrid does not get refreshed from the viewmodel. When I breakpoint the code in my viewmodel, I can see the results in the ObservableCollection has changed per my search, but somehow that is not getting communicated back to the view. Here is my view and viewmodel (BTW, I am using VS2010 RTM):
namespace Attendance.ViewModels
{
public class EmployeeSelectViewModel : ViewModel, INotifyPropertyChanged
{
#region Entity list and constructor
public EmployeeSelectViewModel()
{
{
Initialize();
}
}
private void Initialize()
{
if (employeeRpository == null)
employeeRpository = new EmployeeRepository();
ListOfEmployees = new ObservableCollection<EmployeeDto>(employeeRpository.GetEmployees(true));
}
private EmployeeRepository employeeRpository;
private ObservableCollection<EmployeeDto> listOfEmployees;
public ObservableCollection<EmployeeDto> ListOfEmployees
{
get { return listOfEmployees; }
set
{
if (listOfEmployees != value)
{
listOfEmployees = value;
NotifyPropertyChanged("ListOfEmployee");
}
}
}
private EmployeeDto selectedEmployee;
public EmployeeDto SelectedEmployee
{
get { return selectedEmployee; }
set
{
if (selectedEmployee != value)
{
selectedEmployee = value;
NotifyPropertyChanged("SelectedEmployee");
}
}
}
#endregion
#region UI control references
/// <summary>
/// search text property
/// </summary>
private string searchText;
public string SearchText
{
get { return searchText; }
set
{
if (searchText != value)
{
searchText = value;
NotifyPropertyChanged("SearchText");
}
}
}
public string Location { get; set; }
#endregion
#region Relay Commands
/// <summary>
/// new command
/// </summary>
private ViewCommand newCommand;
public ViewCommand NewCommand
{
get
{
if (newCommand == null)
newCommand = new ViewCommand(param => this.NewEmployee());
return newCommand;
}
}
private void NewEmployee()
{
NavigationActions.NewEmployeeView();
}
/// <summary>
/// edit command
/// </summary>
private ViewCommand editCommand;
public ViewCommand EditCommand
{
get
{
if (editCommand == null)
{
editCommand = new ViewCommand(param => this.EditEmployee());
}
return editCommand;
}
}
private void EditEmployee()
{
NavigationActions.OpenEmployeeView(SelectedEmployee);
}
/// <summary>
/// save command
/// </summary>
private ViewCommand saveCommand;
public ViewCommand SaveCommand
{
get
{
if (saveCommand == null)
{
saveCommand = new ViewCommand(
param => this.SaveEmployee(),
param => this.CanSaveEmployee
);
}
return saveCommand;
}
}
public void SaveEmployee()
{
employeeRpository.SaveChanges();
}
private bool CanSaveEmployee
{
get { return true; }
}
/// <summary>
/// clear search command
/// </summary>
private ViewCommand clearSearchCommand;
public ViewCommand ClearSearchCommand
{
get
{
if (clearSearchCommand == null)
clearSearchCommand = new ViewCommand(param => this.ClearSearch());
return clearSearchCommand;
}
}
private void ClearSearch()
{
this.SearchText = string.Empty;
ListOfEmployees = new ObservableCollection<EmployeeDto>(employeeRpository.GetEmployees(true));
}
/// <summary>
/// search command
/// </summary>
private ViewCommand searchCommand;
public ViewCommand SearchCommand
{
get
{
if (searchCommand == null)
searchCommand = new ViewCommand(param => this.SearchEmployee());
return searchCommand;
}
}
private void SearchEmployee()
{
if (this.SearchText == string.Empty || this.SearchText == null)
{
NavigationActions.ShowError("Search Employees.", "Please enter your search text ...");
return;
}
ListOfEmployees = new ObservableCollection<EmployeeDto>(employeeRpository.GetEmployeesByQuery(SearchText, Location));
}
/// <summary>
/// exit command
/// </summary>
private ViewCommand exitCommand;
public ViewCommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new ViewCommand(param => this.ExitWindow());
}
return exitCommand;
}
}
private void ExitWindow()
{
NavigationActions.CloseCurrentView();
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
}
<Window x:Class="Attendance.Views.EmployeeSelectView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Attendance.ViewModels"
Title="Employee Maintenance" FocusManager.FocusedElement="{Binding ElementName=txtSearchCriteria}"
Height="525" Width="800" WindowStartupLocation="CenterScreen" WindowState="Normal"
WindowStyle="SingleBorderWindow" Icon="Images/gb_icon.png">
<Window.DataContext>
<vm:EmployeeSelectViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/DataGrid.Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--xml data start-->
<XmlDataProvider x:Key="LocationData" XPath="LocationList/LocationItem" Source="XMLData/Location.xml"/>
<!--xml data end-->
</ResourceDictionary>
</Window.Resources>
<Grid Width="775">
<DockPanel HorizontalAlignment="Left" Width="770">
<!-- TOOLBAR -->
<DockPanel DockPanel.Dock="Top" MinHeight="30" Margin="5">
<ToolBar FontWeight="Bold">
<!-- NEW -->
<Button Name="btnNew" Command="{Binding Path=NewCommand}">
<Button.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="SteelBlue" Foreground="White">
Create a new Customer
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
Create a new customer in a new Window tab.
</TextBlock>
<Line Stroke="SteelBlue" StrokeThickness="1" X2="200" />
<StackPanel Orientation="Horizontal">
<Image Margin="2" Source="Images/new.png"/>
<Label>Press F1 for more help</Label>
</StackPanel>
</StackPanel>
</Button.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Source="Images/new.png" Width="22" Height="22" Margin="2"/>
<Label VerticalAlignment="Center">_New</Label>
</StackPanel>
</Button>
<!-- EDIT -->
<Button Name="btnEdit" Command="{Binding Path=EditCommand}">
<Button.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="SteelBlue" Foreground="White">
Edit the current record
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
Edit the current selected Customer.
</TextBlock>
<Line Stroke="SteelBlue" StrokeThickness="1" X2="200" />
<StackPanel Orientation="Horizontal">
<Image Margin="2" Source="Images/dialog-information.png"/>
<Label>Press F1 for more help</Label>
</StackPanel>
</StackPanel>
</Button.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Source="Images/edit.png" Width="22" Height="22" Margin="2" />
<Label VerticalAlignment="Center">_Edit</Label>
</StackPanel>
</Button>
<!-- SEARCH -->
<Separator />
<TextBox Name="txtSearchCriteria"
MinWidth="300" Margin="5"
BorderThickness="1" BorderBrush="LightGray"
FontWeight="Normal" Foreground="Gray" Text="{Binding Path=SearchText}">
</TextBox>
<Button Name="btnSearch" Command="{Binding Path=SearchCommand}">
<Button.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="SteelBlue" Foreground="White">
Search
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
Search a specific Customer.
</TextBlock>
<Line Stroke="SteelBlue" StrokeThickness="1" X2="200" />
<StackPanel Orientation="Horizontal">
<Image Margin="2" Source="Images/find.png"/>
<Label>Press F1 for more help</Label>
</StackPanel>
</StackPanel>
</Button.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Source="Images/find.png" Width="22" Height="22" Margin="2" />
<Label VerticalAlignment="Center">_Find</Label>
</StackPanel>
</Button>
<Button Name="btnClearSearch" Command="{Binding Path=ClearSearchCommand}">
<Button.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="SteelBlue" Foreground="White">
Search
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
Clear search results.
</TextBlock>
<Line Stroke="SteelBlue" StrokeThickness="1" X2="200" />
<StackPanel Orientation="Horizontal">
<Image Margin="2" Source="Images/find.png"/>
<Label>Press F1 for more help</Label>
</StackPanel>
</StackPanel>
</Button.ToolTip>
<StackPanel Orientation="Horizontal">
<Label VerticalAlignment="Center">_Clear Search</Label>
</StackPanel>
</Button>
<!-- EXIT -->
<Separator />
<Button Name="btnExit" Command="{Binding Path=ExitCommand}">
<Button.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="SteelBlue" Foreground="White">
Start the application
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
Start the main application with the M-V-MV pattern.
</TextBlock>
<Line Stroke="SteelBlue" StrokeThickness="1" X2="200" />
<StackPanel Orientation="Horizontal">
<Image Margin="2" Source="Images/dialog-information.png"/>
<Label>Press F1 for more help</Label>
</StackPanel>
</StackPanel>
</Button.ToolTip>
<StackPanel Orientation="Horizontal">
<Image Source="Images/exit.png" Width="22" Height="22" Margin="2" />
<Label VerticalAlignment="Center">_Exit</Label>
</StackPanel>
</Button>
</ToolBar>
</DockPanel>
<!-- LIST -->
<DockPanel DockPanel.Dock="Top" MinHeight="30" Margin="0,0,0,5">
<Label>Location:</Label>
<ComboBox Name="cboLocation" Grid.Column="1" Grid.Row="4"
ItemsSource="{Binding Source={StaticResource LocationData}}"
DisplayMemberPath="location_text" SelectedValuePath="location_value"
SelectedValue="{Binding Path=Location}"
HorizontalAlignment="Left" Width="175" Margin="4" />
</DockPanel>
<DockPanel Margin="5">
<DataGrid ItemsSource="{Binding Path=ListOfEmployees}" AutoGenerateColumns="False" IsReadOnly="True"
Name="dgEmployee" SelectionMode="Single" SelectionUnit="FullRow" CanUserResizeColumns="True"
SelectedItem="{Binding Path=SelectedEmployee}" GridLinesVisibility="Horizontal">
<DataGrid.Columns>
<DataGridTextColumn Header="Employee ID" Width="SizeToCells" MinWidth="125" Binding="{Binding EmployeeID}" />
<DataGridTextColumn Header="First Name" Width="SizeToCells" MinWidth="200" Binding="{Binding FirstName}" />
<DataGridTextColumn Header="Last Name" Width="SizeToCells" MinWidth="200" Binding="{Binding LastName}" />
<DataGridTextColumn Header="Location" Width="SizeToCells" MinWidth="125" Binding="{Binding Location}" />
<DataGridCheckBoxColumn x:Name="Active" Header="Active" Binding="{Binding active}" MinWidth="75" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</DockPanel>
</Grid>
This was my solution:
<DataGrid Name="dgrid" ItemsSource="{Binding UserSettings, IsAsync=True}" AutoGenerateColumns="False">
The key being the setting of IsAsync=True, allows the screen paint to occur
I found 2 ways to refresh DataGrid:
One was posted by VariableLost, it works with INotifyPropertyChanged but have one drawback. Whole DataGrid is refreshed with blink effect (disappears for a split second). Doesn't look natural.
Other solution is to refresh ObservableCollection in ViewModel or Code Behind (if you're not using MVVM):
CollectionViewSource.GetDefaultView(collectionName).Refresh();
Looks more natural because only changes cells affected by edition.
Your previous code should have also worked, but 'magic strings' got in the way.
Property name is ListOfEmployees and in its setter you raise PropertyChanged event with property name ListOfEmployee. 's' is missing.
Beware of your new code. It will raise CollectionChanged event on ListOfEmployees for every insertion, and that might make your app slower if you are doing many insertions.
For many insertions, it would be better to derive from ObservableCollection and implement Reset method that clears underlying Items, adds new items and raises CollectionChanged event of type Reset.
My question was answered in a post on another site. Instead of creating a new instance of the ListOfEmployees in my view model, I just cleared the existing one and added the results from my repository:
private void SearchEmployee()
{
if (String.IsNullOrEmpty(this.SearchText) || String.IsNullOrEmpty(this.Location))
{
NavigationActions.ShowError("Search Employees.", "Please enter your search text and select a location...");
return;
}
// clear the list and repopulate based on the search criteria
if (ListOfEmployees != null)
{
ListOfEmployees.Clear();
IList<EmployeeDto> iList = employeeRpository.GetEmployeesByQuery(SearchText, Location, IsActive);
foreach (EmployeeDto value in iList)
ListOfEmployees.Add(value);
}
}
That did the trick.

Resources