How to reuse control instances in a DataTemplate?

I have several layouts (different data templates) with video controls. The creation of this video control is very long. I want to reuse instances of these video controls in various data patterns.

A contrived example:

Codebehind and ViewModel:

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Layout1 = (DataTemplate)this.FindResource("_layout1"); Layout2 = (DataTemplate)this.FindResource("_layout2"); DataContext = new ViewModel {Content1 = "Content1", Content2 = "Content2"}; } private void Button_Click(object sender, RoutedEventArgs e) { _view.ContentTemplate = _view.ContentTemplate == Layout1 ? Layout2 : Layout1; } DataTemplate Layout1; DataTemplate Layout2; } public class ViewModel { public string Content1 { get; set; } public string Content2 { get; set; } } 

Xaml

 <Window Name="_mainForm" x:Class="WpfVideo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:model="clr-namespace:WpfVideo" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <DataTemplate x:Key="_layout1" DataType="{x:Type model:ViewModel}"> <StackPanel> <Button Content="{Binding Content1}"/> <Button Content="{Binding Content2}"/> </StackPanel> </DataTemplate> <DataTemplate x:Key="_layout2" DataType="{x:Type model:ViewModel}"> <StackPanel Orientation="Horizontal"> <Button Content="{Binding Content1}"/> <Button Content="{Binding Content2}"/> </StackPanel> </DataTemplate> </Window.Resources> <StackPanel> <Button Click="Button_Click">Change</Button> <ContentPresenter Name="_view" Content="{Binding}" ContentTemplate="{StaticResource _layout1}"/> </StackPanel> 

How to reuse buttons and prevent the creation of new buttons each time the template is changed?

EDIT: Toggles between _layout1 and _layout2 data patterns when a button is clicked. Only one template is active. I do not want to draw one control instance in two places. I want to prevent the creation of controls in another template when it is activated (the previous one is deactivated). Or maybe I can use a different approach, without data patterns? Style, resources, triggers?

+4
source share
2 answers

Management pool

 ControlsPoolControl<T> IControlsPool<T> 
  • T is a reusable type.
  • IControlsPool - used to store multiple instances of a key management object (some viewmodel property)
  • ControlsPoolControl - A container that restores IControlsPool's internal control using the associated key.

Implementation

ControlsPoolControl

 public class ControlsPoolControl<T> : UserControl where T : UIElement { private readonly Panel _mainPanel; private T _innerControl; public ControlsPoolControl() { _mainPanel = new Grid(); Content = _mainPanel; } #region Properties #region DependencyProperty public static readonly DependencyProperty KeyObjectProperty = DependencyProperty.Register("KeyObject", typeof(object), typeof(ControlsPoolControl<T>), new PropertyMetadata(null, KeyObjectChanged)); public static readonly DependencyProperty PoolProperty = DependencyProperty.Register("Pool", typeof(IControlsPool<T>), typeof(ControlsPoolControl<T>), new PropertyMetadata(null, PoolChanged)); #endregion public object KeyObject { get { return GetValue(KeyObjectProperty); } set { SetValue(KeyObjectProperty, value); } } public IControlsPool<T> Pool { get { return (IControlsPool<T>)GetValue(PoolProperty); } set { SetValue(PoolProperty, value); } } protected T InnerControl { get { return _innerControl; } set { if (_innerControl == value) return; _innerControl = value; OnControlChanged(); } } #endregion #region Private API void Clear() { _mainPanel.Children.Clear(); } void OnKeyObjectChanged() { UpdateControl(); } void OnControlChanged() { VerifyAccess(); Clear(); var ctrl = InnerControl; if (ctrl != null) _mainPanel.Children.Add(ctrl); } private void UpdateControl() { if (KeyObject == null || Pool == null) InnerControl = null; else InnerControl = Pool.Get(KeyObject); } private static void KeyObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((ControlsPoolControl<T>)d).OnKeyObjectChanged(); } private static void PoolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((ControlsPoolControl<T>)d).UpdateControl(); } #endregion } 

ControlsPool

 public interface IControlsPool<T> where T : UIElement { void Add(object key, T control); T Get(object key); } public class ControlsPool<T> : IControlsPool<T> where T : UIElement { readonly IDictionary<object, T> _controls = new Dictionary<object, T>(); public void Add(object key, T control) { if (key == null) throw new ArgumentNullException("key"); if (_controls.ContainsKey(key)) return; _controls.Add(key, control); } public T Get(object key) { if (key == null) throw new ArgumentNullException("key"); T control = null; if (!_controls.TryGetValue(key, out control)) { control = CreateInstance(key); _controls.Add(key, control); } return control; } protected virtual T CreateInstance(object key) { return Activator.CreateInstance<T>(); } } 

Using

Codebehind

 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ContentControlType = typeof(MyButton); Layout1 = (DataTemplate)FindResource("_layout1"); Layout2 = (DataTemplate)FindResource("_layout2"); DataContext = new ViewModel { Content1 = "Content1", Content2 = "Content2" }; } public Type ContentControlType { get; set; } private void Button_Click(object sender, RoutedEventArgs e) { _view.ContentTemplate = _view.ContentTemplate == Layout1 ? Layout2 : Layout1; } DataTemplate Layout1; DataTemplate Layout2; } public class ViewModel { public string Content1 { get; set; } public string Content2 { get; set; } } public class ButtonsPool : ControlsPool<MyButton> { } public class ButtonPoolControl : ControlsPoolControl<MyButton> { } public class MyButton : Button { static int _counter = 0; public MyButton() { _counter++; } } 

XAML To initialize reuse management, you must use a style.

 <Window Name="_mainForm" x:Class="WpfVideo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:model="clr-namespace:WpfVideo" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <model:ButtonsPool x:Key="_buttonsPool"/> <DataTemplate x:Key="_layout1" DataType="{x:Type model:ViewModel}"> <StackPanel> <model:ButtonPoolControl KeyObject="{Binding Content1}" Pool="{StaticResource _buttonsPool}"> <model:ButtonPoolControl.Resources> <Style TargetType="model:MyButton"> <Setter Property="Content" Value="{Binding Content1}"/> </Style> </model:ButtonPoolControl.Resources> </model:ButtonPoolControl> <model:ButtonPoolControl KeyObject="{Binding Content2}" Pool="{StaticResource _buttonsPool}"> <model:ButtonPoolControl.Resources> <Style TargetType="model:MyButton"> <Setter Property="Content" Value="{Binding Content2}"/> </Style> </model:ButtonPoolControl.Resources> </model:ButtonPoolControl> </StackPanel> </DataTemplate> <DataTemplate x:Key="_layout2" DataType="{x:Type model:ViewModel}"> <StackPanel Orientation="Horizontal"> <model:ButtonPoolControl Pool="{StaticResource _buttonsPool}" KeyObject="{Binding Content1}"> <model:ButtonPoolControl.Resources> <Style TargetType="model:MyButton"> <Setter Property="Content" Value="{Binding Content1}"/> </Style> </model:ButtonPoolControl.Resources> </model:ButtonPoolControl> <model:ButtonPoolControl Pool="{StaticResource _buttonsPool}" KeyObject="{Binding Content2}"> <model:ButtonPoolControl.Resources> <Style TargetType="model:MyButton"> <Setter Property="Content" Value="{Binding Content2}"/> </Style> </model:ButtonPoolControl.Resources> </model:ButtonPoolControl> </StackPanel> </DataTemplate> </Window.Resources> <StackPanel> <Button Click="Button_Click">Change</Button> <ContentPresenter Name="_view" Content="{Binding}" ContentTemplate="{StaticResource _layout1}"/> </StackPanel> 

+3
source

You can not. FrameworkElement and FrameworkContentElement instances can be added to only one point in the logical tree. If you try to add the same instance of such an object to two different places, you will get an exception:

The item already has a logical parent. It must be separated from the old parent before it is attached to the new one.

0
source

Source: https://habr.com/ru/post/1345852/


All Articles