listbox not showing new items - wpf

I have a listbox that i've bound its ItemsSource and SelectedIndex to objects in the ViewModel. I'm also using an extender to auto scroll to the selected item. When I create the list there is some initial data, then I as i move through the list more items are added. The initial data shows up fine and I can scroll through it as I would expect. the problem is that once I add more items the new items don't show up in the listbox. When I move to the new items the list box stops actually scrolling to the selected item, but starts again when I move back. When I look at the ItemsSource in Snoop it shows all of the items, but they aren't displayed. The only items that are display are the original items. So I guess my question is why is this happening?
here is the xaml I'm using for the list box
<ListBox ItemsSource="{Binding Path=Items}"
Width="{Binding Path=Size.SizeW}"
Height="{Binding Path=Size.SizeH}"
SelectedIndex="{Binding Path=FocusedRow, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
IsSynchronizedWithCurrentItem="True"
extenders:ListBoxExtenders.AutoScrollToCurrentItem="True">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Height"
Value="{Binding Path=RowHeight}" />
<Setter Property="Width"
Value="{Binding Path=RowWidth}" />
<Setter Property="Margin"
Value="{Binding Path=RowSpacing}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<m:CanvasItemsControl ItemTemplateSelector="{StaticResource ResourceKey=listViewItemTemplateSelector}"
Visibility="Visible"
ItemsSource="{Binding Path=ListItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Height="{Binding Path=RowHeight}"
Width="{Binding Path=RowWidth}"
ClipToBounds="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</m:CanvasItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here is the code for the extender
public class ListBoxExtenders : DependencyObject
{
public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToCurrentItemProperty);
}
public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToCurrentItemProperty, value);
}
public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var listBox = s as ListBox;
if (listBox != null)
{
var listBoxItems = listBox.Items;
if (listBoxItems != null)
{
var newValue = (bool)e.NewValue;
var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));
if (newValue)
{ listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker; }
else
{ listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker; }
}
}
}
public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
{
if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
{
listBox.ScrollIntoView(listBox.Items[index]);
}
}
}
I'm not sure what other information would be helpful so if there is something else you need to see let me know and I'll add it.
Listbox SelectedIndex is bound to FocusedRow
private uint focusedRow;
public uint FocusedRow
{
get
{ return focusedRow; }
set
{
if (value == focusedRow)
{ return; }
focusedRow = value;
base.RaisePropertyChanged("FocusedRow");
}
}
ItemsSource is bound to Items
private ObservableCollection<DisplayList> items;
public ObservableCollection<DisplayList> Items
{
get
{ return items; }
set
{
if (value == items)
{ return; }
items = value;
base.RaisePropertyChanged("Items");
}
}

Make sure that new items have valid RowHeight and RowWidth. That can be the problem since while snooping you could see your data.

Make sure that your "Items" you bound as ItemsSource of your listbox is an ObservableCollection<> and not a simple List.
it should work !

Related

WPF ListView Select and get Item Focus automatically

I'm don't know how to get focused item automatically change within a ListView.
I would like the focused item in the view to automatically change when I change the "IsSelected" property to an other element in the databinded list:
When an item is modified by PC/SC card reader (see this as input), the next element should be focused like this:
I would like to stay in MVVM and therefor not having View referenced in the ViewModel. Below is my current code.
Model : The main purpose is to extend a DTO with an IsSelected property and implementing INotifyPropertyChanged
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsSelected;
private DtoReader _dtoReader;
public SmartDeviceModel(DtoReader _reader)
{
_dtoReader = _reader;
}
public string DisplayName => _dtoReader.DisplayName;
public string Uid
{
get
{
return _dtoReader.Uid;
}
set
{
_dtoReader.Uid = value;
OnPropertyChanged("Uid");
}
}
public long RadioId
{
get
{
return _dtoReader.RadioId : _dtoMarker.RadioId;
}
set
{
_dtoReader.RadioId = value;
OnPropertyChanged("RadioId");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
ViewModel received events of a PC/SC card reader to pair data from RFID chip with current selected item. When RFID chip is removed from PC/SC Reader, the next element is well selected but got not focused.
public class ScanDeviceViewModel : BaseViewModel
{
public BindingList<SmartDeviceModel> ReaderList { get; }
public int SelectedReaderIndex;
private ITagReaderInput _rfidReader;
public ScanDeviceViewModel()
{
//Get Data listener for RFID Tag
_rfidReader = new IdentivTagReader.IdentivTagReader();
// Data Source of DTO
SiteInteractor siteInterractor = new SiteInteractor();
// List used for DataBinding
ReaderList = new BindingList<SmartDeviceModel>();
foreach (DtoReader m in SiteInteractor.GetReaders().OrderBy(x => x.DisplayName))
{
ReaderList.Add(new SmartDeviceModel(m));
}
if (ReaderList.Count() > 0)
{
for (var i = 0; i < ReaderList.Count(); i++)
{
if (String.IsNullOrEmpty(ReaderList[i].Uid))
{
SelectedReaderIndex = i;
ReaderList[i].IsSelected = true;
break;
}
}
}
_rfidReader.LabelDetected += RfidTagDetected;
_rfidReader.LabelRemoved += RfidRemoved;
}
private void RfidTagDetected(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex] != null && string.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid))
{
ReaderList[SelectedReaderIndex].IsSelected = true;
ReaderList[SelectedReaderIndex].Uid = tag.Uid;
ReaderList[SelectedReaderIndex].RadioId = tag.RadioId;
}
}
private void RfidRemoved(ITagLabel tag)
{
if (ReaderList[SelectedReaderIndex].Uid == tag.Uid)
{
ReaderList[SelectedReaderIndex].IsSelected = false;
while (ReaderList.Count >= SelectedReaderIndex + 1)
{
SelectedReaderIndex++;
if (String.IsNullOrEmpty(ReaderList[SelectedReaderIndex].Uid)){
ReaderList[SelectedReaderIndex].IsSelected = true;
break;
}
}
}
}
}
View I'm using a "Setter" using databinding to my model property "IsSelected" as suggested here but I most missed something else I don't understand yet.
<ListView ItemsSource="{Binding ReaderList}"
Margin="5" x:Name="listViewReader" SelectionMode="Single"
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox Grid.Row ="0" Stretch="Uniform" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxHeight="90">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="{Binding DisplayName}" />
<DockPanel Grid.Row="1">
<Label Content="UID"/>
<Label Content="{Binding Uid}"/>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="1">
<Label Content="RadioID" />
<Label Content="{Binding RadioId}"/>
</DockPanel>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I tried several approach like this answer, although item is well selected, it is not focused.
I finally figure it out. Below is my current working code.
In the Model I have just changed the flag IsSelected to IsCurrent to avoid confusion with ListViewItem built-in property but it might just be an implementation detail.
public class SmartDeviceModel : INotifyPropertyChanged
{
public bool IsCurrent;
[...]
}
The BindingList in ViewModel is mostly the same as in OP:
public class ScanDeviceViewModel : INotifyPropertyChanged
{
public BindingList<SmartDeviceModel> ReaderList { get; internal set; }
[...]
}
NB : BindingList seems to reduce OnNotifyPropertyChange need but other Type of List should work with a tiny bit of extra code. I also noticed BindingList might not be suited for huge list scenario.
The View is then using the above ViewModel as DataContext and therefore Binding ItemSource to the BindingList. The ListViewItem Style Setter is then using the IsCurrent Property from the Model.
<ListView ItemsSource="{Binding ReaderList}"
SelectionMode="Single"
SelectionChanged="OnListViewSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsCurrent}" />
</Style>
</ListView.ItemContainerStyle>
[...]
And finally this piece of View Code behind below is mainly to simulate the focus as per user input, otherwise the elemant get selected but not focused and might be outside the visible item scope :
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView listView = e.Source as ListView;
if (listView.ItemContainerGenerator.ContainerFromItem(listView.SelectedItem) is FrameworkElement container)
{
container.Focus();
}
}
According to MVVM you can implement custom Interaction Behavior:
Import to XAML: xmlns:b="http://schemas.microsoft.com/xaml/behaviors" (if you are using .NET Core 3.1 - 5)
Add to content-body:
<ListView ...>
<b:Interaction.Behaviors>
<local:AutoScrollToLastItemBehavior />
</b:Interaction.Behaviors>
</ListView>
Finally add the next class:
public sealed class AutoScrollToLastItemBehavior : Microsoft.Xaml.Behaviors.Behavior<ListView>
{
// Need to track whether we've attached to the collection changed event
bool _collectionChangedSubscribed = false;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += SelectionChanged;
// The ItemSource of the listView will not be set yet,
// so get a method that we can hook up to later
AssociatedObject.DataContextChanged += DataContextChanged;
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ScrollIntoView();
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ScrollIntoView();
}
private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The ObservableCollection implements the INotifyCollectionChanged interface
// However, if this is bound to something that doesn't then just don't hook the event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && !_collectionChangedSubscribed)
{
// The data context has been changed, so now hook
// into the collection changed event
collection.CollectionChanged += CollectionChanged;
_collectionChangedSubscribed = true;
}
}
private void ScrollIntoView()
{
int count = AssociatedObject.Items.Count;
if (count > 0)
{
var last = AssociatedObject.Items[count - 1];
AssociatedObject.ScrollIntoView(last);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= SelectionChanged;
AssociatedObject.DataContextChanged -= DataContextChanged;
// Detach from the collection changed event
var collection = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collection != null && _collectionChangedSubscribed)
{
collection.CollectionChanged -= CollectionChanged;
_collectionChangedSubscribed = false;
}
}
}

Combobox inside DataGrid, hide selected item on Combobox

What I am trying to accomplish is to have a combo box that in a row on the DataGrid. Each of the comboboxes are bound to the same ItemsSource. I would like to have the end user be able to click on an item in the combobox and for that specific combobox, have that item be the selected item. And the rest of the comboboxes will then hide the selected value. I am still new to XAML though I do understand the whole MVVM pattern (which is what I am using).
XAML:
<DataGridTemplateColumn Header="Database Column" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.DatabaseMappingFields, ElementName=Root}" DisplayMemberPath="ColumnName">
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="Visibility" Value="{Binding IsSelected, Converter={StaticResource InverseBoolToVis}}"/>
</Style>
</ComboBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding DataContext.CommandDatabaseHeaderSelected, ElementName=Root}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
C#:
SetDatabaseColumns(new List<string>()
{ "Item1", "Item2", "Item3", "Item4", "Item5", "Item6" });
public void SetDatabaseColumns(List<string> databaseColumnNames)
{
DatabaseMappingFields.Clear();
DatabaseMappingFields.Add(new DatabaseColumnsModel() { ColumnName = "<ignore>" });
foreach (var item in databaseColumnNames)
{
DatabaseMappingFields.Add(new DatabaseColumnsModel() { ColumnName = item });
}
}
public ICommand CommandDatabaseHeaderSelected
{
get
{
return _commandDatabaseHeaderSelected ??
(_commandDatabaseHeaderSelected = new RelayCommand<SelectionChangedEventArgs>(DatabaseHeaderSelected));
}
}
private void DatabaseHeaderSelected(SelectionChangedEventArgs args)
{
var selectedItem = args.AddedItems.Count >= 1
? args.AddedItems[0] as DatabaseColumnsModel
: null;
var previousItem = args.RemovedItems.Count >= 1
? args.RemovedItems[0] as DatabaseColumnsModel
: null;
var selectedFileMapping = Data.TextFileMapping.FirstOrDefault(w => w == SelectedFileMapping);
if (selectedItem == null) return;
var isSelected = IsSelectedItemEnabled(selectedItem, previousItem);
selectedItem.IsSelected = isSelected;
selectedFileMapping.DatabaseColumn = isSelected ? selectedItem : null;
if (previousItem == null) return;
previousItem.IsSelected = false;
}
public class DatabaseColumnsModel : ObservableObject
{
private string _columnName;
private bool _isSelected;
private int _index;
public int Index
{
get { return _index; }
set { Set(() => Index, ref _index, value); }
}
public string ColumnName
{
get { return _columnName; }
set { Set(() => ColumnName, ref _columnName, value); }
}
public bool IsSelected
{
get { return _isSelected; }
set { Set(() => IsSelected, ref _isSelected, value); }
}
}
Since we are making a desktop app and would like to have a common look and feel around all of our apps, we have decided to use WPF and CefSharp and use WPF as a shell and the front end will be HTML based and use a WPF CefSharp control to display a browser window inside of WPF.

Get selected TreeViewItem in MVVM

I want make TreeView with editable nodes. I googled this good, as I think, article:
http://www.codeproject.com/Articles/31592/Editable-TextBlock-in-WPF-for-In-place-Editing
But I have a problems. My TreeView formed dinamically, not statically as in the arcticle. Like that
<TreeView Name="_packageTreeView" Margin="5" ItemsSource="{Binding PackageExtendedList}">
<TreeView.InputBindings>
<KeyBinding Key="C" Command="{Binding *TestCommand*}" CommandParameter="{Binding}" />
</TreeView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type MasterBuisnessLogic:RootDocPackage}" ItemsSource="{Binding Path=Childs}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition>
</ColumnDefinition>
<ColumnDefinition>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="/Resources/DocGroup.png"></Image>
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}"></Etb:EditableTextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
PackageExtendedList - List of DocPackageExtended.
So, first question - how can I get TreeViewItem instance in TestCommand? Not instance DocPackageExtended class! I want to get instance selected TreeViewItem like in the article.
And second question - After I get instance TreeViewItem, how can I get EditableTextBlock from the TreeView item's DataTemplate.
added answer
I already tried it. Cause in MVVM ViewModel cannot has any link to View object like TreeView, I make handler in code-behind, like that
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
// Already have TreeViewItem instance without of ItemContainerGenerator help
var tvi = e.OriginalSource as TreeViewItem;
if (tvi == null)
return;
var etb = VisualTreeLib.VisualTreeLib.GetVisualChild<EditableTextBlock>(tvi);
if (etb == null)
return;
// Do what I want
etb.IsEditable = true;
}
Unfortunately, this has no any affect :(
I also tried that approach, but also failed.
in DocPackageExtended type I define property
public bool IsEditable
{
get { return _isEditable; }
set
{
_isEditable = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsEditable"));
}
}
than change in XAML:
<Etb:EditableTextBlock Margin="5,0,0,0" Grid.Column="1" Text="{Binding Path=Name}" *IsEditable="{Binding Path=IsEditable}"*/>
and in ViewModel
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
}
Doesn't work too :(
Any ideas?
This might help you.
private void Button_Click(object sender, RoutedEventArgs e)
{
TreeViewItem treeViewItemFound = GetItem(MyTreeview, MyTreeview.SelectedItem);
ContentPresenter header = treeViewItemFound.Template.FindName("PART_Header", treeViewItemFound) as ContentPresenter;
if (header != null)
{
TextBox myTextBox = (TextBox)header.ContentTemplate.FindName("MyTextBox", header);
}
}
public TreeViewItem GetItem(ItemsControl container, object itemToSelect)
{
foreach (object item in container.Items)
{
if (item == itemToSelect)
{
return (TreeViewItem)container.ItemContainerGenerator.ContainerFromItem(item);
}
else
{
ItemsControl itemContainer = (ItemsControl)container.ItemContainerGenerator.ContainerFromItem(item);
if (itemContainer.Items.Count > 0)
{
TreeViewItem treeViewItemFound = GetItem(itemContainer, itemToSelect);
if (treeViewItemFound != null)
return treeViewItemFound;
}
}
}
return null;
}
First question: Since it seems that you can select multiple entries, you need to filter all selected entries in TestCommand's executed method:
IEnumerable<DocPackageExtended> selectedEntries = PackageExtendedList.Where(d => d.IsSelected);
If multiple selection is disabled, you could bind the TreeView's selected item to a property in your VM and access this property in TestCommand's method.
Second question: You get the dataitem's container through var container = YourTreeViewInstance.ItemContainerGenerator.ContainerFromItem(dataInstance);. Now you have to go through this container with the help of the VisualTreeHelper until it finds a control of type EditableTextBlock. But I wouldn't do this in a ViewModel, rather in a helper-class or with the help of attached properties.
EDIT: You're binding the IsEditable property of the instances in the Childs property of your DocPackageExtended class to your EditableTextBox, but in your TestCommandMethod you're manipulating the IsEditableproperty of a DocPackageExtended instance directly. You could do the following:
private void TestCommandMethod(object obj)
{
var dpe = obj as DocPackageExtended;
if (dpe == null)
return;
dpe.IsEditable = true;
foreach (RootDocPackage rdp in dpe.Childs)
{
rdp.IsEditable = true;
}
}

WPF Datagrid SelectedItem loses binding after edit

EDITED: As comments have suggested, I should implement the MVVM pattern, I have done exactly that. However the same problem still persists. So I have altered the question accordingly:
I have a datagrid containing two columns bound to an observable collection (in the MyNotes class). One column contains a combobox and the other a textbox. The collection stores references to Note objects that contain an enumeration variable (displayed by the combobox) and a string (displayed by the textbox). All works fine except for the SelectedItems (and therefore the SelectedItem). When the program is built and run, you can add new rows to the datagrid (using the add/remove buttons) but after you attempt an edit (by entering the datagrid's textbox or combobox) then the datagrid's selectedItems and selectedItem fail. This can be seen by the use of the add/remove buttons: the selected row is not deleted and a new row is not added above the selected row respectively. This is a result of a symptom regarding the SelectedNote property losing its binding (I don't know why this happens and when I attempt to hack a rebind, the rebind fails?). Another symptom relates to the selected items property not reflecting what the datagrid is actually showing as selected (when viewing it in debug mode).
I am sure this problem relates to an issue with the datagrid, which makes it unusable for my case.
Here is the new XAML (its datacontext, the viewmodel, is set in the XAML and ParaTypes and headerText are both XAML static resources):
<DataGrid x:Name ="dgdNoteLimits"
ItemsSource ="{Binding ParagraphCollection}"
SelectedItem ="{Binding Path=SelectedNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop ="True"
HeadersVisibility ="Column"
AutoGenerateColumns ="False"
CanUserAddRows ="False"
CanUserReorderColumns ="False"
CanUserSortColumns ="False"
BorderThickness ="0"
VerticalGridLinesBrush ="DarkGray"
HorizontalGridLinesBrush="DarkGray"
SelectionMode ="Extended"
SelectionUnit ="FullRow"
ColumnHeaderStyle ="{StaticResource headerText}">
<DataGrid.ItemContainerStyle>
<Style>
<Style.Resources>
<!-- SelectedItem's background color when focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Blue"/>
<!-- SelectedItem's background color when NOT focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Blue" />
</Style.Resources>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn Header = "Note Type"
ItemsSource = "{Binding Source={StaticResource ParaTypes}}"
SelectedValueBinding= "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBinding = "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth = "115"
Width = "Auto">
</DataGridComboBoxColumn>
<DataGridTextColumn Header ="Description"
Binding="{Binding Path=NoteText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width ="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value ="true" />
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Margin="0, 0, 0, 16"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Add"
Width="72"
Margin="16,8,8,8"
Command="{Binding AddClickCommand}"/>
<Button Content="Remove"
Width="72"
Margin="16,8,8,8"
Command="{Binding RemoveClickCommand}"/>
</StackPanel>
Here is the view model:
class MainWindowViewModel : INotifyPropertyChanged
{
MyNotes NotesCollection;
private bool canExecute;
private ICommand clickCommand;
public MainWindowViewModel()
{
this.NotesCollection = new MyNotes();
this.ParagraphCollection = this.NotesCollection.Notes;
this.canExecute = true;
}
private ObservableCollection<Note> paragraphCollection;
public ObservableCollection<Note> ParagraphCollection
{
get { return this.paragraphCollection; }
set
{
this.paragraphCollection = value;
RaisePropertyChanged(() => this.ParagraphCollection);
}
}
private Note selectedNote;
public Note SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
RaisePropertyChanged(() => this.SelectedNote);
}
}
public ICommand AddClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => AddButtonHandler(), canExecute));
}
}
public void AddButtonHandler()
{
int noteIndex = 0;
Note aNote;
// what to do if a note is either selected or unselected...
if (this.SelectedNote != null)
{
// if a row is selected then add row above it.
if (this.SelectedNote.NoteIndex != null)
noteIndex = (int)this.SelectedNote.NoteIndex;
else
noteIndex = 0;
//create note and insert it into collection.
aNote = new Note(noteIndex);
ParagraphCollection.Insert(noteIndex, aNote);
// Note index gives sequential order of collection
// (this allows two row entries to have same NoteType
// and NoteText values but still note equate).
int counter = noteIndex;
// reset collection index so they are sequential
for (int i = noteIndex; i < this.NotesCollection.Notes.Count; i++)
{
this.NotesCollection.Notes[i].NoteIndex = counter++;
}
}
else
{
//if a row is not selected add it to the bottom.
aNote = new Note(this.NotesCollection.Count);
this.ParagraphCollection.Add(aNote);
}
}
public ICommand RemoveClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => RemoveButtonHandler(), canExecute));
}
}
public void RemoveButtonHandler()
{
//delete selected note.
this.ParagraphCollection.Remove(selectedNote);
}
//boiler plate INotifyPropertyChanged implementation!
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<System.Func<T>> propertyExpression)
{
var memberExpr = propertyExpression.Body as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("propertyExpression should represent access to a member");
string memberName = memberExpr.Member.Name;
RaisePropertyChanged(memberName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and the model:
class MyNotes
{
public ObservableCollection<Note> Notes;
public MyNotes()
{
this.Notes = new ObservableCollection<Note>();
}
}
public enum NoteTypes
{
Header, Limitation, Warning, Caution, Note
}
public class Note
{
public int? NoteIndex { get; set; }
public NoteTypes NoteType { get; set; }
public string NoteText { get; set; }
public Note()
{
this.NoteIndex = null;
this.NoteType = NoteTypes.Note;
this.NoteText = "";
}
public Note(int? noteIndex): this()
{
this.NoteIndex = noteIndex;
}
public override string ToString()
{
return this.NoteType + ": " + this.NoteText;
}
public override bool Equals(object obj)
{
Note other = obj as Note;
if (other == null)
return false;
if (this.NoteIndex != other.NoteIndex)
return false;
if (this.NoteType != other.NoteType)
return false;
if (this.NoteText != other.NoteText)
return false;
return true;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + this.NoteIndex.GetHashCode();
hash = hash * 23 + this.NoteType.GetHashCode();
hash = hash * 23 + this.NoteText.GetHashCode();
return hash;
}
}
Existing comments were greatly appreciated (I have learnt a lot and see the value of MVVM). It is just a shame they have not resolved the problem. But thank you.
So if anyone knows how I can resolve this issue, then that would be greatly appreciated.

TreeView Sync to SelectedItem in View Model

I have a ViewModel on top of a WPF TreeView control. I want the ViewModel to be able to set and read the SelectedItem from the TreeView. However, the SelectedItem property of the TreeView is not bindable.
I am able to set and get the selected item in the code behind (using the ItemContainerGenerator and TreeViewItem.IsSelected = true) but this leads to some ugly communication between the code behind and the ViewModel.
Does anyone have a clean solution for this?
I can provide an example.
What I do is setting the IsSelected property of a TreeViewItem (not the TreeView itself) in the view model, because you can bind to this.
In my view model I have a property ElementInViewModel which is a data structure that forms a tree itself.
I use a HierarchicalDataTemplate in my Xaml to display it.
The data object itself is of type YourDomainType and its child elements (of the same type) are in its ChildElements property.
In the view model, I set the IsExpanded and IsSelected property of my data class YourDomainType. Because of the style defined below, they will pass this setting to the TreeViewItem.
Does this work for you?
<UserControl>
<UserControl.Resources>
<CollectionViewSource Source="{Binding Path=ElementInViewModel}" x:Key="Cvs">
</CollectionViewSource>
<HierarchicalDataTemplate DataType="{x:Type DomainModel:YourDomainType}"
ItemsSource="{Binding Path=ChildElements}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</Setter>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</Setter>
</Style>
</UserControl.Resources>
<DockPanel>
<TreeView ItemsSource="{Binding Source={StaticResource Cvs}}"/>
</DockPanel>
</UserControl>
You can use some kind of proxy class to bind SelectedItem property to In property and Out property bind to your ViewModel:
public class Proxy : FrameworkElement
{
public static readonly DependencyProperty InProperty;
public static readonly DependencyProperty OutProperty;
public Proxy()
{
Visibility = Visibility.Collapsed;
}
static Proxy()
{
var inMetadata = new FrameworkPropertyMetadata(
delegate(DependencyObject p, DependencyPropertyChangedEventArgs args)
{
if (null != BindingOperations.GetBinding(p, OutProperty))
{
var proxy = p as Proxy;
if (proxy != null)
proxy.Out = args.NewValue;
}
});
inMetadata.BindsTwoWayByDefault = false;
inMetadata.DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
InProperty = DependencyProperty.Register("In",
typeof (object),
typeof (Proxy),
inMetadata);
var outMetadata = new FrameworkPropertyMetadata(
delegate(DependencyObject p, DependencyPropertyChangedEventArgs args)
{
ValueSource source = DependencyPropertyHelper.GetValueSource(p, args.Property);
if (source.BaseValueSource != BaseValueSource.Local)
{
var proxy = p as Proxy;
if (proxy != null)
{
var expected = proxy.In;
if (!ReferenceEquals(args.NewValue, expected))
{
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.DataBind, new Action(delegate
{
proxy.Out = proxy.In;
}));
}
}
}
});
outMetadata.BindsTwoWayByDefault = true;
outMetadata.DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
OutProperty = DependencyProperty.Register("Out",
typeof (object),
typeof (Proxy),
outMetadata);
}
public object In
{
get { return GetValue(InProperty); }
set { SetValue(InProperty, value); }
}
public object Out
{
get { return GetValue(OutProperty); }
set { SetValue(OutProperty, value); }
}
}
<Proxy In="{Binding ElementName=Tree, Path=SelectedItem}" Out="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"/>
<TreeView x:Name="Tree" ItemsSource="{Binding Path=Items}"/>

Resources