'How to reuse controls instances in DataTemplate?
I have few layouts (different DataTemplates) with video controls. Creation of this video controls is very long. I want to reuse instances of this video controls in different DataTemplates.
Farfetched 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 creation of new buttons on each template change?
EDITED: Used switch between datatemplates _layout1 and _layout2 on Button_click. Only one template active. I don’t want to draw one instance of control in two places. I want to prevent creation of controls in other template when it activated (previous deactivated). Or maybe I can use another approach, without datatemplates? Style, resources, triggers?
Solution 1:[1]
Controls Pool
ControlsPoolControl<T>
IControlsPool<T>
- T – type of reusable control.
- IControlsPool - used to store reusable instances of control by key object (some property of viewmodel)
- ControlsPoolControl - Container, which restore inner control from IControlsPool by binded 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>();
}
}
Usage
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 reusable control, you should use 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>
</Window>
Solution 2:[2]
You cannot. FrameworkElement and FrameworkContentElement instances can only be added to one point of the logical tree. If you attempt to add the same instance of such an object to two different places you 'll get an exception saying:
Element already has a logical parent. It must be detached from the old parent before it is attached to a new one.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Rand Random |
| Solution 2 | Jon |
