<...">

Get a DataTemplate from a data object in a ListBox

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 that looks like this:

 public Column(string name, bool isVisibleInTable) { Name = name; IsVisibleInTable = isVisibleInTable; } public string Name { get; set; } public bool IsVisibleInTable { get; set; } 

EditableTextBlock is a UserControl that, when double-clicked, rotates to the TextBox and returns to the TextBlock when Lost Focus. It also has a property called IsInEditMode , which is false by default. When true, a TextBox displayed.

Question:
ItemsSouce ListBox is an ObservableCollection<Column> . I have a button that adds a new Column to the collection. But my problem is that I want IsInEditMode display true for the newly added EditableTextBlock this button. But I can only access Column in the ViewModel. How can I access the EditableTextBlock specified Column in the ItemsSource collection?

The only solution I can come up with is to infer the class from Column and add a property for it (for example: name: IsInEditMode ) (or possibly a wrapper class. Here is a similar answer that involves using a wrapper class) and bind to this property in a DataTemplate as follows:

 <DataTemplate DataType="local:DerivedColumn"> <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}" IsInEditMode="{Binding IsInEditMode}"/> </DataTemplate> 

But I do not want this. I want to somehow do this in XAML without class allocation and adding unnecessary code. (And also compliance with MVVM rules)

+6
source share
2 answers

If you have the option of adding a new dependency property to your EditableTextBlock user control, you might consider adding a name that has the name StartupInEditMode , the default for false , to preserve the existing behavior.

The Loaded handler for UserControl can then determine the status of StartupInEditMode to decide how to initially set the value to 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 value of the StartupInEdit change StartupInEdit not matter, because it is evaluated only once at creation. This means that you can populate the ListBox collection, where each EditableTextBlock not in edit mode, then change the StartupInEditmMode mode to True when you start adding new items. Then, each new EditableTextBlock control is launched in edit mode.

You can accomplish this switch in behavior by specifying a DataTemplate , where the Binding this new property points to a view variable, not to collection elements.

  <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 what is used as the container for the view) called ANewViewProperty in this example. This value may be part of your view model if you change the binding to {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}} .

This new property ( ANewViewProperty ) does not even need to implement INotifyPropertyChanged , because the binding will get the initial value, because it creates a new EditableTextBlock control, and if the value changes later, it will not affect it.

You set the value of ANewViewProperty to false when loading the ListBox ItemSource initially. When you click the button to add a new item to the list, set ANewViewProperty to True , which means the control will now be created, starting from the edit mode.

Update: C # - view-only alternative

An alternative to view-only, view-only (similar to answer2946329 answer) is a binding to the ListBox.ItemContainerGenerator.ItemsChanged handler, which will be launched when a new item is added. After launching, you are now acting on new elements (via Boolean DetectingNewItems ), which finds the first EditableTextBlock control for the corresponding ListBoxItem visual container for the newly added element. When you have a link for the control, change 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 references the ViewModel, assuming you save the 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; } } ..... 
+2
source

To get an element inside a template and change its properties in the 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; } 
+1
source

All Articles