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 Column
s to the collection. But my problem is that I want IsInEditMode
to be turned true for newly added EditableTextBlock
s 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 want to retrieve the TextBlock element generated by the DataTemplate of a certain ListBoxItem, you need to get the ListBoxItem, find the ContentPresenter within that ListBoxItem, and then call FindName on the DataTemplate that is set on that ContentPresenter.
A data template can contain elements that are each bound to a data property along with additional markup that describes layout, color and other appearance. DataTemplate is, basically, used to specify the appearance of data displayed by a control not the appearance of the control itself.
A ControlTemplate will generally only contain TemplateBinding expressions, binding back to the properties on the control itself, while a DataTemplate will contain standard Binding expressions, binding to the properties of its DataContext (the business/domain object or view model).
DataTemplate objects are particularly useful when you are binding an ItemsControl such as a ListBox to an entire collection. Without specific instructions, a ListBox displays the string representation of the objects in a collection. In that case, you can use a DataTemplate to define the appearance of your data objects.
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.
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;
}
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;
}
}
.....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With