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

4

我是一个有用的助手,可以翻译文本。

我有几个布局(不同的DataTemplates)带有视频控件。创建这些视频控件非常耗时。我想在不同的DataTemplates中重复使用这些视频控件实例。

牵强的例子:

代码后台和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>

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

编辑:在按钮单击事件中使用datatemplate之间的开关_layout1和_layout2。只有一个模板处于活动状态。我不想在两个地方绘制一个控件实例。我希望在激活其他模板时(先前取消激活),可以防止在其他模板中创建控件。或者我可以使用另一种方法,而不使用datatemplates?样式、资源、触发器?


可以在XAML中完成。在资源中定义一个ContentControl,其中包含您想要共享的控件,并在数据模板中使用它作为StaticResource加载。 - Maciek Świszczowski
2个回答

4

Controls Pool

ControlsPoolControl<T> 
IControlsPool<T>
  • T - 可重用控件的类型。
  • IControlsPool - 通过键对象(视图模型的某个属性)存储可重用控件实例的容器。
  • ControlsPoolControl - 容器,通过绑定的键从IControlsPool中恢复内部控件。

实现

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
}

控件池

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

我成功地使用了这个解决方案。我在 OnControlChanged 中添加了以下内容来处理将子控件与其父控件断开连接的问题(虽然我对此解决方案并不满意,但它对我们来说是实用的)。`((ctrl as FrameworkElement)?.Parent as Panel)?.Children.Remove(ctrl); if ((ctrl as FrameworkElement)?.Parent is ContentControl contentControl) { contentControl.Content = null; }if ((ctrl as FrameworkElement)?.Parent is Decorator decorator) { decorator.Child = null; }` - Terrence

0

不行。只能将FrameworkElementFrameworkContentElement实例添加到逻辑树的一个位置。如果您尝试将同一实例的此类对象添加到两个不同的位置,则会收到异常消息:

元素已经有逻辑父级。 必须从旧的父级中分离出来, 然后才能附加到新的父级。


请查看我的帖子中的编辑内容。我认为在逻辑树的不同点使用相同实例没有问题。 - SeeSharp
每次我为 _view 的 ContentTemplate 属性初始化旧的 DataTemplate 实例(_layout1 或 _layout2),更改 DataTemplate 都会创建新的按钮。 - SeeSharp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接