如何在DataTemplate中重用控件实例?

时间:2011-03-30 09:48:31

标签: wpf caching wpf-controls datatemplate

我的视频控件布局很少(不同的DataTemplates)。创建此视频控件的时间非常长。我想在不同的DataTemplates中重用此视频控件的实例。

Farfetched示例:

Codebehind和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>

如何重复使用按钮并阻止在每个模板更改时创建新按钮?

已编辑:在Button_click上使用datatemplates _layout1和_layout2之间切换。只有一个模板有效。我不想在两个地方绘制一个控件实例。我希望在激活时禁止在其他模板中创建控件(之前已停用)。 或者我可以使用另一种方法,没有datatemplates?风格,资源,触发器?

2 个答案:

答案 0 :(得分:3)

控制池

ControlsPoolControl<T> 
IControlsPool<T>
  • T型可重复使用控件。
  • IControlsPool - 用于存储 密钥控制的可重用实例 object(viewmodel的某些属性)
  • ControlsPoolControl - 容器, 从中恢复内部控制 IControlsPool by binded key。

实施

<强> 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>();
    }
}

用法

<强>代码隐藏

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 要初始化可重用控件,您应该使用样式。

<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>

答案 1 :(得分:0)

你做不到。 FrameworkElementFrameworkContentElement个实例只能添加到逻辑树的一个点。如果您尝试将此类对象的相同实例添加到两个不同的位置,则会出现例外情况:

  

Element已经有一个逻辑父级。   它必须脱离旧的   父级在附加到新的之前   之一。