Get DataTemplate from data object in ListBox - c#

I have a ListBox whose ItemTemplate looks like this:
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Column is a simple class which looks like this:
public Column(string name, bool isVisibleInTable)
{
Name = name;
IsVisibleInTable = isVisibleInTable;
}
public string Name { get; set; }
public bool IsVisibleInTable { get; set; }
The EditableTextBlock is a UserControl that turns into a TextBox when double clicked and turns back into a TextBlock when Lost Focus. It also has a Property called IsInEditMode which is by default false. When it is true, TextBox is shown.
The Question:
The ItemsSouce of the ListBox is an ObservableCollection<Column>. I have a button which adds new Columns to the collection. But my problem is that I want IsInEditMode to be turned true for newly added EditableTextBlocks by that Button. But I can only access Column in the ViewModel. How will I access the EditableTextBlock of the specified Column in the ItemsSource collection?
The only solution I can come up with is deriving a class from Column and adding a property for that (eg: name: IsInEditMode) (Or maybe a wrapper class. Here's a similar answer which suggestes using a wrapper class) and Binding to that property in the DataTemplate like so:
<DataTemplate DataType="local:DerivedColumn">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
But I don't want this. I want some way to do this in XAML without deriving classes and adding unnecessary code. (And also adhering to MVVM rules)

If you have scope to add a new dependency property to the EditableTextBlock user control you could consider adding one that has the name StartupInEditMode, defaulting to false to keep the existing behavior.
The Loaded handler for the UserControl could then determine the status of StartupInEditMode to decide how to initially set the value of IsInEditMode.
//..... Added to EditableTextBlock user control
public bool StartupInEdit
{
get { return (bool)GetValue(StartupInEditProperty); }
set { SetValue(StartupInEditProperty, value); }
}
public static readonly DependencyProperty StartupInEditProperty =
DependencyProperty.Register("StartupInEdit", typeof(bool), typeof(EditableTextBlock ), new PropertyMetadata(false));
private void EditableTextBlock_OnLoaded(object sender, RoutedEventArgs e)
{
IsInEditMode = StartupInEditMode;
}
For controls already in the visual tree the changing value of StartupInEdit does not matter as it is only evaluated once on creation. This means you can populate the collection of the ListBox where each EditableTextBlock is not in edit mode, then swap the StartupInEditmMode mode to True when you start adding new items. Then each new EditableTextBlock control starts in the edit mode.
You could accomplish this switch in behavior by specifying a DataTemplate where the Binding of this new property points to a variable of the view and not the collection items.
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock"
Text="{Binding Name, Mode=TwoWay}"
StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
You need to add a property to the parent Window (or Page or whatever is used as the containter for the view) called ANewViewProperty in this example. This value could be part of your view model if you alter the binding to {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}.
This new property (ANewViewProperty) does not even need to implement INotifyPropertyChanged as the binding will get the initial value as it is creating the new EditableTextBlock control and if the value changes later it has no impact anyway.
You would set the value of ANewViewProperty to False as you load up the ListBox ItemSource initially. When you press the button to add a new item to the list set the value of ANewViewProperty to True meaning the control that will now be created starting up in edit mode.
Update: The C#-only, View-only alternative
The code-only, view-only alternative (similar to user2946329's answer)is to hook to the ListBox.ItemContainerGenerator.ItemsChanged handler that will trigger when a new item is added. Once triggered and you are now acting on new items (via Boolean DetectingNewItems) which finds the first descendant EditableTextBlock control for the appropriate ListBoxItem visual container for the item newly added. Once you have a reference for the control, alter the IsInEditMode property.
//.... View/Window Class
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
MyListBox.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
}
private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
if ((e.Action == NotifyCollectionChangedAction.Add) && DetectingNewItems)
{
var listboxitem = LB.ItemContainerGenerator.ContainerFromIndex(e.Position.Index + 1) as ListBoxItem;
var editControl = FindFirstDescendantChildOf<EditableTextBlock>(listboxitem);
if (editcontrol != null) editcontrol.IsInEditMode = true;
}
}
public static T FindFirstDescendantChildOf<T>(DependencyObject dpObj) where T : DependencyObject
{
if (dpObj == null) return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dpObj); i++)
{
var child = VisualTreeHelper.GetChild(dpObj, i);
if (child is T) return (T)child;
var obj = FindFirstChildOf<T>(child);
if (obj != null) return obj;
}
return null;
}
Update #2 (based on comments)
Add a property to the view that refers back to the the ViewModel assuming you keep a reference to the View Model in the DataContext:-
..... // Add this to the Window/Page
public bool DetectingNewItems
{
get
{
var vm = DataContext as MyViewModel;
if (vm != null)
return vm.MyPropertyOnVM;
return false;
}
}
.....

To get an element inside a template and change it's properties in code you need FrameworkTemplate.FindName Method (String, FrameworkElement) :
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Then:
for (int i = 0; i < yourListBox.Items.Count; i++)
{
ListBoxItem yourListBoxItem = (ListBoxItem)(yourListBox.ItemContainerGenerator.ContainerFromIndex(i));
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(yourListBoxItem);
DataTemplate myDataTemplate = contentPresenter.ContentTemplate;
EditableTextBlock editable = (EditableTextBlock) myDataTemplate.FindName("editableTextBlock", contentPresenter);
//Do stuff with EditableTextBlock
editable.IsInEditMode = true;
}

Related

Virtualization and SelectionChanged event

I am using SelectionChanged event of ListBox, but it "doesn't work".
Here is repro:
public partial class MainWindow : Window
{
readonly List<Item> _items = new List<Item>
{
new Item(),
... // add 20 more, to have selected item outside of visible region
new Item(),
new Item { IsSelected = true },
new Item(),
};
void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) =>
Debug.WriteLine($"Changed {e.AddedItems.Count}/{e.RemovedItems.Count}");
void button_Click(object sender, RoutedEventArgs e) =>
listBox.ItemsSource = listBox.ItemsSource == null ? _items : null;
}
public class Item
{
public bool IsSelected { get; set; }
}
and xaml:
<Grid>
<ListBox x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Test" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="10" Click="button_Click" />
</Grid>
1. Disable virtualization
Add to list:
VirtualizingPanel.IsVirtualizing="False"
Clicking button will produce the output. Cool.
2. VirtualizationMode="Standard"
Remove that line, by default ListBox will use "Standard" virtualization:
Event is not triggered. I need to scroll to selected item to have event triggered.
3. VirtualizationMode="Recycling"
Change virtualization to:
VirtualizingPanel.VirtualizationMode="Recycling"
WTF? Even scrolling doesn't trigger event.
Question: How to get SelectionChanged event to work properly in the most performant mode without need to scroll as in "Standard" mode?
With virtualization, if an item doesn't have a container (ListBoxItem) associated with it, then there's no container to which that ItemContainerStyle is applied. That means your IsSelected binding won't be applied until the item is scrolled into view. Until that property is set, no selection change occurs, and SelectionChanged is not raised.
How to get SelectionChanged event to work properly in the most performant mode without need to scroll as in "Standard" mode?
It arguably *is* working properly. If you approach this from an MVVM angle, then you need not rely on events from UI elements. Track the item selection yourself, in your model. You could use a utility class like this:
public interface ISelectable
{
bool IsSelected { get; set; }
}
public class ItemEventArgs<T> : EventArgs
{
public T Item { get; }
public ItemEventArgs(T item) => this.Item = item;
}
public class SelectionTracker<T> where T : ISelectable
{
private readonly ObservableCollection<T> _items;
private readonly ObservableCollection<T> _selectedItems;
private readonly ReadOnlyObservableCollection<T> _selectedItemsView;
private readonly HashSet<T> _trackedItems;
private readonly HashSet<T> _fastSelectedItems;
public SelectionTracker(ObservableCollection<T> items)
{
_items = items;
_selectedItems = new ObservableCollection<T>();
_selectedItemsView = new ReadOnlyObservableCollection<T>(_selectedItems);
_trackedItems = new HashSet<T>();
_fastSelectedItems = new HashSet<T>();
_items.CollectionChanged += OnCollectionChanged;
}
public event EventHandler<ItemEventArgs<T>> ItemSelected;
public event EventHandler<ItemEventArgs<T>> ItemUnselected;
public ReadOnlyObservableCollection<T> SelectedItems => _selectedItemsView;
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems == null)
goto default;
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldItems == null || e.NewItems == null)
goto default;
RemoveItems(e.OldItems.OfType<T>());
AddItems(e.NewItems.OfType<T>());
break;
case NotifyCollectionChangedAction.Move:
break;
default:
Refresh();
break;
}
}
public void Refresh()
{
RemoveItems(_trackedItems);
AddItems(_items);
}
private void AddItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged += OnItemPropertyChanged;
_trackedItems.Add(item);
UpdateItem(item);
}
}
private void RemoveItems(IEnumerable<T> items)
{
foreach (var item in items)
{
var observableItem = item as INotifyPropertyChanged;
if (observableItem != null)
observableItem.PropertyChanged -= OnItemPropertyChanged;
_trackedItems.Remove(item);
UpdateItem(item);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is T item)
UpdateItem(item);
}
private void UpdateItem(T item)
{
if (item?.IsSelected == true && _trackedItems.Contains(item))
{
if (_fastSelectedItems.Add(item))
{
_selectedItems.Add(item);
this.ItemSelected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
else
{
if (_fastSelectedItems.Remove(item))
{
_selectedItems.Remove(item);
this.ItemUnselected?.Invoke(this, new ItemEventArgs<T>(item));
}
}
}
}
When you create your ObservableCollection of items, instantiate a SelectionTracker for that collection. Then subscribe to ItemSelected and ItemUnselected to handle individual selection changes, or alternatively subscribe to SelectedItems.CollectionChanged. If you don't care about being able to access SelectedItems as a collection, then you can get rid of _selectedItems and _selectedItemsView and avoid some list removal overhead.
[With VirtualizationMode="Recycling"] WTF? Even scrolling doesn't trigger event.
Well, that's a strange one. I see no reason why that should not work in this case, but I can perhaps see why it might not always work. In theory, as soon as the container is 'recycled' and its DataContext is assigned a new item, the IsSelected binding should update. If the container's previously assigned item had also been selected, that might not trigger a property change, and thus the event might not fire. But that doesn't seem to be the case in your example. Possibly a bug or unintended consequence of how recycling is implemented.
Don’t use IsSelected to manage selection.
I think the big takeaway here is that using ListBoxItem.IsSelected to *set* the selection is unreliable; it should only be trusted to reflect whether a given container is selected. It’s really intended for style and template triggers, so that they may know whether to render a container as selected or not. It was never meant to manage selection, and it’s a mistake to use it that way, because it represents the selection state of the container and not its associated data item. Thus, it only works in the most naïve and least performant scenario where every item is always associated with its own container (no virtualization).

How can I properly reset a value associated with a ComboBox, from within a PropertyChanged event?

I have a ComboBox that is bound to a property on my ViewModel (from hear on "VM".) When a user makes a selection on the ComboBox it properly updates the bound property in my VM. Within my UI code, I have subscribed to the PropertyChanged event on my VM.
As it should behave, when the user makes a selection within the ComboBox, my PropertyChanged event is correctly executing in my UI back-end code. When the UI code catches the change of this property, under certain selection conditions I need to halt the process and request the user for additional information. From the UI, I send them a dialog. If they cancel the dialog, I reset the value in the VM that is associated with the ComboBox controls SelectedValue.
This is what I've observed. When the operation is cancelled by the user, my VM property is being set to the new value. However, the ComboBox is still showing the text value of the original entry that has now changed. How can I force the ComboBox to update itself from within my PropertyChanged event? In this case, I think it's just a textual issue or numeric index change that's referencing the text data from the bound collection. The data is correct in the VM but the display value for the ComboBox is wrong.
EXAMPLE
ComboBox Details
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
Height="27" />
Sorry for the wordy properties on the VM, but that's to explain what's happening. My ListOfComboBoxDisplayObjects collection represents a set of enumerator values that are stored in the path within SelectedValuePath. The descriptive text for each value is pulled from the ListOfComboBoxDisplayObjects which is a special list strictly created for the UI. This basically pairs an enumeration value with a meaningful description.
ListOfComboBoxDisplayObjects Definition (from within VM)
Edit #1 - Added this definition to my example
private ObservableCollection<BindableEnumerationItem<Values>> _listOfComboBoxDisplayObjects;
public ObservableCollection<BindableEnumerationItem<Values>> ListOfComboBoxDisplayObjects
{
get { return _listOfComboBoxDisplayObjects; }
private set
{
if (value != _listOfComboBoxDisplayObjects)
{
_listOfComboBoxDisplayObjects= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ListOfComboBoxDisplayObjects)));
}
}
}
MySelectionIsAnEnumeration Definition (From within VM)
*Edit #1: Adding this code definition.
private Values_mySelectionIsAnEnumeration ;
public Values MySelectionIsAnEnumeration
{
get { return _mySelectionIsAnEnumeration; }
set
{
//Double-checked this-- value is different on the second-call to change this value, once the UI cancels the operation.
if (value != _mySelectionIsAnEnumeration)
{
_mySelectionIsAnEnumeration= value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(MySelectionIsAnEnumeration )));
}
}
}
Pertinent Values Associated with ListOfComboBoxDisplayObjects
These values are generated in the ctor of the VM. They are fixed throughout the application.
Item #1
Text: "This is a Foo!"
Value: Values.Foo
Item #2:
Text: "Hi, I'm Bar."
Value: Values.Bar
Item #3:
Text: "This is Baz. I need to ask a question before I can be used."
Value: Values.Baz
PropertyChanged Event - From the UI Back-End
private void VM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MySelectionIsAnEnumeration":
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if(!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
break;
}
}
After executing the above code, what I'm observing is that the VM.MySelectionIsAnEnumeration value is indeed changing to the proper value of Value.Foo when a user cancels the operation within AskAQuestionAndGetAResult(). However, after it's finished the ComboBox still reads "This is Baz. I need to ask a question before I can be used.", which is obviously the display value associated with Value.Baz.
How can I update both the underlying VM property AND the display text on the CombobBox to correctly show the valued that is now stored in VM.MySelectionIsAnEnumeration?
Edit #2
Below is the code efor my BindableEnumerationItem that I use within my Observable Collections for comboxes and list boxes. This is used throughout my application in simpler cases and has caused no issue. Please note, this is my actual, unaltered code. I've not renamed anything. My comboboxes can bind to each Item property for a type-safe property and DisplayText is the descriptor text.
public class BindableEnumerationItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private T _item;
public BindableEnumerationItem(T item, string displayText)
{
_item = item;
_displayText = displayText;
}
private string _displayText;
public string DisplayText
{
get { return _displayText; }
set
{
if (value != _displayText)
{
_displayText = value;
PropertyChanged(this, new PropertyChangedEventArgs("DisplayText"));
}
}
}
public T Item
{
get { return _item; }
set
{
_item = value;
PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
}
create an extension that will wire up the command from your viewmodel in xaml to the selector, which in this case is the combobox.
public partial class Extensions
{
public static readonly DependencyProperty SelectionChangedCommandProperty = DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Selector;
if (element != null)
{
element.SelectionChanged -= OnSelectionChanged;
if (e.NewValue != null)
{
element.SelectionChanged += OnSelectionChanged;
}
}
}));
public static ICommand GetSelectionChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(SelectionChangedCommandProperty);
}
public static void SetSelectionChangedCommand(UIElement element, ICommand value)
{
element.SetValue(SelectionChangedCommandProperty, value);
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var element = sender as Selector;
var command = element.GetValue(SelectionChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element.SelectedItem))
{
command.Execute(element.SelectedItem);
e.Handled = true;
}
}
}
Create the command in the viewmodel that handles the value changed event.
public ICommand EnumerationValueChangedCommand
{
get
{
return new Command(
() =>
{
if (VM.MySelectionIsAnEnumeration == Values.Baz)
{
//Prompt the user and get DialogResult.
bool answerGiven = AskAQuestionAndGetAResult();
if (!answerGiven)
VM.MySelectionIsAnEnumeration = Values.Foo;
}
});
}
}
And then bind using that extension. ext is the namespace for your extensions.
<ComboBox
ItemsSource="{Binding ListOfComboBoxDisplayObjects}"
SelectedValue="{Binding MySelectionIsAnEnumeration}"
DisplayMemberPath="Text"
SelectedValuePath="EnumerationValue"
ext:Extensions.SelectionChangedCommand="{Binding EnumerationValueChangedCommand}"
Height="27" />

Get selected items from a list box with check boxes

A have a list box with check boxes (I removed the part about alignment, width, margin as not related to the case):
<ListBox
ItemsSource ="{Binding SItemCollection}"
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Item.Code}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
I have a class SItem inside my ViewModel, which stores two fields - CachedStr which I get from Cache and a Boolean IsChecked which represents whether the item is checked or not (CachedStr object also has several fields (Name, Code etc), I've chosen to show the Code):
public class SItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public CachedStr Item { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChanged("IsChecked");
}
}
protected void NotifyPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
A have a Collection of SItems (SItemCollection), which fills my ListBox with items, some of which are ticked. This collection is outside the SItem class, it is inside my view model. I also have a set of all items (AvailableSItems) that should be available in the ListBox and a set of items that should be checked (ItemsToBeTicked) at the very beginning. This two sets contain objects of type CachedStr. By using those sets I get my SItemCollection:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
ObservableCollectionEx<SItem> strItems = new ObservableCollectionEx<SItem>();
this.AvailableSItems.ForEach(p =>
{
SItem item = new SItem();
item.Item = p;
item.IsChecked = false;
strItems.Add(item);
});
strItems.ForEach(p =>
{
if (this.ItemsToBeTicked.Contains(p.Item))
{
p.IsChecked = true;
}
else p.IsChecked = false;
}
);
return strItems;
}
}
The above-mentioned code works. But I also need a method which will get the final set of all ticked items (after, for example, pressing the button), and that's where I'm stuck. I do get a notifications when I tick or untick something.
The code currently creates a new instance of the collection in the get block. This has to be changed, otherwise the changes done in the UI will be reverted each time the get block is called.
Take the code which is currently in the get block, extract it to a method and use the return value of the method to set your SItemCollection property.
In constructor for example:
SItemCollection = CreateInitialCollection();
And the property will be simplyfied to:
public ObservableCollectionEx<SItem> SItemCollection
{
get
{
return _sitemCollection;
}
set
{
if (_sitemCollection!= value)
{
_sitemCollection= value;
RaisePropertyChanged("SItemCollection");
}
}
}
ObservableCollectionEx<SItem> _sitemCollection;
When this is fixed (and if the binding to the IsChecked property in SItem works), you can use a Linq expression:
var checkedItems = SItemCollection.Where(item => item.IsChecked == true)

Using controls in a LongListSelector

I'm using a LongListSelector to display a list of complex objects and update the datatemplate (a control) depending on the number of items in the bound objects list property.
I have tried the following.
Accessing the data in the OnItemRealized event to try and get the bound control and update it via a method call.
Not sure if possible
Adding a property to the control being bound which adds controls to a wrap panel when the property is set.
The set accessor in the controls property is never hit.
Hopefully it's clear what im trying to achieve.
Is it possible to call functionality in a property being bound to as shown in my control
If not is it possible to access the control being bound and call exposed functionality that way
If anyone could shed any light on my issue i would greatly appreciate it!
Data Template
<DataTemplate x:Key="LLS_SomeTemplate" >
<MyApp:ObjectTemplate SomeObjects="{Binding SomeEntities}"/>
</DataTemplate>
Object being bound
public class SomeObject
{
public ObservableCollection<Entities> _SomeEntities { get; set; }
public ObservableCollection<Entities> SomeEntities
{
get
{
if (_SomeEntities == null)
_SomeEntities = new ObservableCollection<Entities>();
return _SomeEntities;
}
set
{
_SomeEntities = value;
}
}
public SomeObject()
{
}
}
Control Property
public static DependencyProperty SomeObjectsProperty = DependencyProperty.Register("SomeObjects", typeof(ObservableCollection<Entities>), typeof(ObjectTemplate), new PropertyMetadata(new ObservableCollection<Entities>()));
public ObservableCollection<SomeObject> SomeObjects
{
get
{
return (ObservableCollection<SomeObject>)GetValue(SomeObjectsProperty);
}
set
{
SetValue(SomeObjectsProperty, value);
if (value != null && value.Count > 0)
{
foreach (SomeObject eLink in value)
{
//Add a new control to a wrap panel for each object in the list
}
}
}
}
There are few ways how CLR set up dependency properties. You must avoid perform operations in setter. Create value changed event handler instead:
public static DependencyProperty SomeObjectsProperty = DependencyProperty.Register("SomeObjects", typeof(ObservableCollection<Entities>), typeof(ObjectTemplate), new PropertyMetadata(new ObservableCollection<Entities>(), new PropertyChangedCallback(OnSomeObjectsPropertyChanged));
private static void OnSomeObjectsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as ObjectTemplate).UpdateSomeObjects(e.NewValue as SomeObjects);
}
public void UpdateSomeObjects(SomeObjects value)
{
if (value != null && value.Count > 0)
{
foreach (SomeObject eLink in value)
{
//Add a new control to a wrap panel for each object in the list
}
}
}
Hope it helps you to solve your problem

WPF DataGrid SelectedItem binding stops working after item change

My problem/situation is very similar to Wpf DataGrid SelectedItem loses binding after cell edit but I'm not using any "custom" WPF framework. I have a model that implements INotifyPropertyChanged and IEditableObject, and a grid bound to an ObservableCollection<T>. The grid's SelectedItem property is bound to a property on the VM.
With a break point, I can see my ViewModel.SelectedItem property change as I select different rows in the grid. The moment I change a value on a row, however, the ViewModel.SelectedItem property is no longer set as I change focus on the rows. The solution identified in the above link does not work since I'm not using a custom WPF framework, just naked WPF.
Any ideas?
// View model area
public IPurchaseorderItem SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
SelectItemCommand.NotifyCanExecuteChanged();
RemoveItemCommand.NotifyCanExecuteChanged();
}
}
}
// XAML SelectedItem binding
<views:NoBindingGroupDataGrid SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
// Special Grid to clear binding groups (read on a similarly themed SO question/answer)
internal sealed class NoBindingGroupDataGrid : DataGrid
{
private bool _editing = false;
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
var desiredSize = base.MeasureOverride(availableSize);
ClearBindingGroup();
return desiredSize;
}
protected override void OnCellEditEnding(DataGridCellEditEndingEventArgs e)
{
base.OnCellEditEnding(e);
if (!_editing)
{
_editing = true;
CommitEdit(DataGridEditingUnit.Row, true);
_editing = false;
}
}
private void ClearBindingGroup()
{
ItemBindingGroup = null;
foreach (var item in Items)
{
var row = ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (row != null)
{
row.BindingGroup = null;
}
}
}
}
Apparently the SelectedItem dependency property on DataGrid is broken and not being used correctly. After some debugging using OnPropertyChanged, I found that the grid is actually setting a "CurrentItem" property reliably. I changed to use CurrentItem instead and everything appears to work correctly... the user's "selected row" is being sent to the VM without incident.

Categories

Resources