Сделать шаблон данных DataTemplate из объекта данных в ListBox
у меня есть ListBox
чей ItemTemplate
выглядит так:
<DataTemplate DataType="local:Column">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Column
- Это простой класс, который выглядит так:
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. Когда это правда, это.
Вопрос:
The ItemsSouce
из списка-это ObservableCollection<Column>
. У меня есть кнопка, которая добавляет новый Column
s в коллекции. Но моя проблема в том, что я хочу IsInEditMode
чтобы быть истинным для недавно добавленных EditableTextBlock
С кнопка. Но я могу только получить доступ Column
в ViewModel. Как я получу доступ к EditableTextBlock
указанного Column
на ItemsSource
коллекция?
единственное решение, которое я могу придумать-это производный класс от Column
и добавление свойства для этого (например: name:IsInEditMode
) (или, возможно, класса. здесьаналогичный ответ, который предлагает использовать класс-оболочку) и привязку к этому свойству в DataTemplate следующим образом:
<DataTemplate DataType="local:DerivedColumn">
<utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
но я не хочу этого. Я хочу каким-то образом сделать это в XAML без получения классов и добавления ненужного кода. (А также соблюдение правил MVVM)
2 ответов
если у вас есть область для добавления нового свойства зависимостей в EditableTextBlock
user control вы можете рассмотреть возможность добавления одного, который имеет имя StartupInEditMode
научиться false
сохранить существующее поведение.
на Loaded
обработчик для элемента UserControl
затем можно определить статус 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
control, и если значение изменится позже, оно все равно не повлияет.
вы установили бы значение из ANewViewProperty
to False
как вы загрузите ListBox
ItemSource
изначально. При нажатии кнопки для добавления нового элемента в список установите значение ANewViewProperty
to True
означает элемент управления, который теперь будет создан при запуске в режиме редактирования.
Update: альтернатива только для C#, только для просмотра
альтернатива только для кода, только для просмотра (аналогично ответу user2946329) - подключить к ListBox.ItemContainerGenerator.ItemsChanged
обработчик, который будет срабатывать при добавлении нового элемента. Однажды сработал и ты теперь действуют на новые элементы (через Boolean DetectingNewItems
), который находит первого потомка EditableTextBlock
управление для соответствующего ListBoxItem
визуальный контейнер для вновь добавленного элемента. Как только у вас есть ссылка для элемента управления, измените 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;
}
}
.....
чтобы получить элемент внутри шаблона и изменить его свойства в коде, вам нужно 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;
}