从 ListBox 中的数据对象获取 DataTemplate
Get DataTemplate from data object in ListBox
我有一个 ListBox
,它的 ItemTemplate
看起来像这样:
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Column
是一个简单的 class,看起来像这样:
public Column(string name, bool isVisibleInTable)
{
Name = name;
IsVisibleInTable = isVisibleInTable;
}
public string Name { get; set; }
public bool IsVisibleInTable { get; set; }
EditableTextBlock
是一个 UserControl
,双击时变成 TextBox
,失去焦点时又变回 TextBlock
。它还有一个名为 IsInEditMode
的 属性,默认情况下为 false。如果为真,则显示 TextBox
。
问题:
ListBox 的 ItemsSouce
是一个 ObservableCollection<Column>
。我有一个按钮,可以将新的 Column
添加到集合中。但我的问题是,我希望 IsInEditMode
通过 那个按钮 为新添加的 EditableTextBlock
变为真。但是我只能在 ViewModel 中访问 Column
。如何访问 ItemsSource
集合中指定 Column
的 EditableTextBlock
?
我能想出的唯一解决方案是从 Column
导出 class 并为此添加一个 属性(例如:名称:IsInEditMode
)(或者可能是包装器 class。Here 的类似答案建议使用包装器 class) 并绑定到 DataTemplate 中的那个 属性,如下所示:
<DataTemplate DataType="local:DerivedColumn">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
但我不想要这个。我想要一些方法在 XAML 中执行此操作,而无需派生 classes 并添加不必要的代码。 (并且还遵守 MVVM 规则)
要获取模板中的元素并在代码中更改它的属性,您需要 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;
}
然后:
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;
}
如果您可以向 EditableTextBlock
用户控件添加一个新的依赖项 属性,您可以考虑添加一个名称为 StartupInEditMode
的依赖项,默认为 false
保持现有行为。
UserControl
的 Loaded
处理程序然后可以确定 StartupInEditMode
的状态,以决定如何初始设置 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;
}
对于已经在可视化树中的控件,StartupInEdit
的变化值无关紧要,因为它只在创建时评估一次。这意味着您可以填充 ListBox
的集合,其中每个 EditableTextBlock
都不处于编辑模式,然后在开始添加新项目时将 StartupInEditmMode
模式切换为 True
。然后每个新的 EditableTextBlock
控件在编辑模式下启动。
您可以通过指定 DataTemplate
来完成此行为切换,其中此新 属性 的 Binding
指向视图的变量而不是集合项。
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock"
Text="{Binding Name, Mode=TwoWay}"
StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
您需要添加一个 属性 到父级 Window
(或 Page
或任何用作视图容器的对象),在此示例中称为 ANewViewProperty
.如果您将绑定更改为 {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}
.
,则此值可能是您的视图模型的一部分
这个新的 属性 (ANewViewProperty
) 甚至不需要实现 INotifyPropertyChanged
因为绑定将在创建新的 EditableTextBlock
控件时获得初始值如果以后更改该值,则无论如何都没有影响。
您可以在最初加载 ListBox
ItemSource
时将 ANewViewProperty
的值设置为 False
。当您按下按钮将新项目添加到列表时,将 ANewViewProperty
的值设置为 True
,这意味着现在将在编辑模式下启动创建的控件。
更新:仅 C#,View-only 替代方案
code-only、view-only 替代方案(类似于 user2946329 的回答)是挂钩 ListBox.ItemContainerGenerator.ItemsChanged
处理程序,该处理程序将在添加新项目时触发。一旦触发,您现在正在对新项目进行操作(通过布尔值 DetectingNewItems
),它会为新添加的项目找到适当的 ListBoxItem
视觉容器的第一个后代 EditableTextBlock
控件。获得控件引用后,更改 IsInEditMode
属性.
//.... 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;
}
更新 #2(基于评论)
在视图中添加一个 属性 来引用 ViewModel,假设您在 DataContext
中保留对视图模型的引用:-
..... // Add this to the Window/Page
public bool DetectingNewItems
{
get
{
var vm = DataContext as MyViewModel;
if (vm != null)
return vm.MyPropertyOnVM;
return false;
}
}
.....
我有一个 ListBox
,它的 ItemTemplate
看起来像这样:
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Column
是一个简单的 class,看起来像这样:
public Column(string name, bool isVisibleInTable)
{
Name = name;
IsVisibleInTable = isVisibleInTable;
}
public string Name { get; set; }
public bool IsVisibleInTable { get; set; }
EditableTextBlock
是一个 UserControl
,双击时变成 TextBox
,失去焦点时又变回 TextBlock
。它还有一个名为 IsInEditMode
的 属性,默认情况下为 false。如果为真,则显示 TextBox
。
问题:
ListBox 的 ItemsSouce
是一个 ObservableCollection<Column>
。我有一个按钮,可以将新的 Column
添加到集合中。但我的问题是,我希望 IsInEditMode
通过 那个按钮 为新添加的 EditableTextBlock
变为真。但是我只能在 ViewModel 中访问 Column
。如何访问 ItemsSource
集合中指定 Column
的 EditableTextBlock
?
我能想出的唯一解决方案是从 Column
导出 class 并为此添加一个 属性(例如:名称:IsInEditMode
)(或者可能是包装器 class。Here 的类似答案建议使用包装器 class) 并绑定到 DataTemplate 中的那个 属性,如下所示:
<DataTemplate DataType="local:DerivedColumn">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
但我不想要这个。我想要一些方法在 XAML 中执行此操作,而无需派生 classes 并添加不必要的代码。 (并且还遵守 MVVM 规则)
要获取模板中的元素并在代码中更改它的属性,您需要 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;
}
然后:
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;
}
如果您可以向 EditableTextBlock
用户控件添加一个新的依赖项 属性,您可以考虑添加一个名称为 StartupInEditMode
的依赖项,默认为 false
保持现有行为。
UserControl
的 Loaded
处理程序然后可以确定 StartupInEditMode
的状态,以决定如何初始设置 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;
}
对于已经在可视化树中的控件,StartupInEdit
的变化值无关紧要,因为它只在创建时评估一次。这意味着您可以填充 ListBox
的集合,其中每个 EditableTextBlock
都不处于编辑模式,然后在开始添加新项目时将 StartupInEditmMode
模式切换为 True
。然后每个新的 EditableTextBlock
控件在编辑模式下启动。
您可以通过指定 DataTemplate
来完成此行为切换,其中此新 属性 的 Binding
指向视图的变量而不是集合项。
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock"
Text="{Binding Name, Mode=TwoWay}"
StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</DataTemplate>
您需要添加一个 属性 到父级 Window
(或 Page
或任何用作视图容器的对象),在此示例中称为 ANewViewProperty
.如果您将绑定更改为 {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}
.
这个新的 属性 (ANewViewProperty
) 甚至不需要实现 INotifyPropertyChanged
因为绑定将在创建新的 EditableTextBlock
控件时获得初始值如果以后更改该值,则无论如何都没有影响。
您可以在最初加载 ListBox
ItemSource
时将 ANewViewProperty
的值设置为 False
。当您按下按钮将新项目添加到列表时,将 ANewViewProperty
的值设置为 True
,这意味着现在将在编辑模式下启动创建的控件。
更新:仅 C#,View-only 替代方案
code-only、view-only 替代方案(类似于 user2946329 的回答)是挂钩 ListBox.ItemContainerGenerator.ItemsChanged
处理程序,该处理程序将在添加新项目时触发。一旦触发,您现在正在对新项目进行操作(通过布尔值 DetectingNewItems
),它会为新添加的项目找到适当的 ListBoxItem
视觉容器的第一个后代 EditableTextBlock
控件。获得控件引用后,更改 IsInEditMode
属性.
//.... 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;
}
更新 #2(基于评论)
在视图中添加一个 属性 来引用 ViewModel,假设您在 DataContext
中保留对视图模型的引用:-
..... // Add this to the Window/Page
public bool DetectingNewItems
{
get
{
var vm = DataContext as MyViewModel;
if (vm != null)
return vm.MyPropertyOnVM;
return false;
}
}
.....