XAML中的动态内容 [英] Dynamic content in xaml

查看:115
本文介绍了XAML中的动态内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我想根据条件显示某些东西,那么简单的方法是使用可见性绑定:

If I want to display something based on condition, then the simple approach is to use visibility binding:

<Something Visibility="{Binding ShowSomething, Converter=..." ... />

使用这种方法,可视树仍会创建,如果会导致性能问题某事具有复杂的结构(许多子项,绑定,事件,触发器等)。

With this approach the visual tree is still created and can cause performance issues if Something has complicated structure (many children, bindings, events, triggers, etc.).

更好的方法是通过触发器添加内容:

A better approach is to add content via trigger:

<ContentControl>
    <ContentControl.Style>
        <Style TargetType="ContentControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding ShowSomething}" Value="SomeValue">
                    <Setter Property="Content">
                        <Setter.Value>
                            <Something ... />
                        </Setter.Value>
                    </Setter>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

但这是一场噩梦,同意吗?拥有多个这样的动态部件将污染xaml,使其难以导航。

But that's a nightmare, agree? Having multiple of such dynamic parts will pollute xaml and make it hard to navigate.

还有其他方法吗?

我会尽可能使用数据模板,但会创建专用的 Type 小类型和在xaml中定义的实际数据模板对我来说听起来很糟糕。

I am using data-templates whenever I can, but creating a dedicated Type and actually defining data-template is too much when dynamic part simply depends on a value of property. Of course that property can be refactored into a type, which then can use its own data-template, but meh. I'd really prefer to not do this every time, too many small types and actual data-temples defined in xaml sounds same bad to me.

我实际上喜欢第二种方法,但是我想改进它,例如通过进行xaml-extension或自定义控件。我决定提出问题是因为:1)我很懒;)2)我不确定什么是最好的方式3)我确定其他人(xaml大师)已经解决了这个问题。

I actually like the second approach, but I'd like to improve it, e.g. by making xaml-extension or maybe custom control. I decide to ask question because: 1) I am lazy ;) 2) I am not sure what is the best way 3) I am sure others (xaml masters) have this problem solved already.

推荐答案

我能想到的大多数可重用的解决方案是创建自定义控件并将其内容包装在 ControlTemplate ,以便在需要时进行延迟加载。

Most reusable solution I can think of is to create custom control and wrap its content in a ControlTemplate, so that it is lazy-loaded when needed.

以下是示例实现:

[ContentProperty(nameof(Template))]
public class ConditionalContentControl : FrameworkElement
{
    protected override int VisualChildrenCount => Content != null ? 1 : 0;

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (Content != null)
        {
            if (ShowContent)
                Content.Arrange(new Rect(finalSize));
            else
                Content.Arrange(new Rect());
        }
        return finalSize;
    }

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index > VisualChildrenCount - 1)
            throw new ArgumentOutOfRangeException(nameof(index));
        return Content;
    }

    private void LoadContent()
    {
        if (Content == null)
        {
            if (Template != null)
                Content = (UIElement)Template.LoadContent();
            if (Content != null)
            {
                AddLogicalChild(Content);
                AddVisualChild(Content);
            }
        }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var desiredSize = new Size();
        if (Content != null)
        {
            if (ShowContent)
            {
                Content.Measure(constraint);
                desiredSize = Content.DesiredSize;
            }
            else
                Content.Measure(new Size());
        }
        return desiredSize;
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property == ShowContentProperty)
        {
            if (ShowContent)
                LoadContent();
        }
        else if (e.Property == TemplateProperty)
        {
            UnloadContent();
            Content = null;
            if (ShowContent)
                LoadContent();
        }
    }

    private void UnloadContent()
    {
        if (Content != null)
        {
            RemoveVisualChild(Content);
            RemoveLogicalChild(Content);
        }
    }

    #region Dependency properties

    private static readonly DependencyPropertyKey ContentPropertyKey = DependencyProperty.RegisterReadOnly(
        nameof(Content),
        typeof(UIElement),
        typeof(ConditionalContentControl),
        new FrameworkPropertyMetadata
        {
            AffectsArrange = true,
            AffectsMeasure = true,
        });
    public static readonly DependencyProperty ContentProperty = ContentPropertyKey.DependencyProperty;
    public static readonly DependencyProperty ShowContentProperty = DependencyProperty.Register(
        nameof(ShowContent),
        typeof(bool),
        typeof(ConditionalContentControl),
        new FrameworkPropertyMetadata
        {
            AffectsArrange = true,
            AffectsMeasure = true,
            DefaultValue = false,
        });
    public static readonly DependencyProperty TemplateProperty = DependencyProperty.Register(
        nameof(Template),
        typeof(ControlTemplate),
        typeof(ConditionalContentControl),
        new PropertyMetadata(null));

    public UIElement Content
    {
        get => (UIElement)GetValue(ContentProperty);
        private set => SetValue(ContentPropertyKey, value);
    }

    public ControlTemplate Template
    {
        get => (ControlTemplate)GetValue(TemplateProperty);
        set => SetValue(TemplateProperty, value);
    }

    public bool ShowContent
    {
        get => (bool)GetValue(ShowContentProperty);
        set => SetValue(ShowContentProperty, value);
    }

    #endregion
}

注意该实现不会在加载内容后立即将其卸载,而只是将其排列为(0,0)大小。为了在不应该显示时从视觉树中卸载内容,我们需要进行一些修改(此代码示例仅限于修改后的代码):

Note that this implementation does not unload the content once it is loaded, but merely arranges it so that it is of (0,0) size. In order to unload the content from visual tree when it is not supposed to be shown, we need to make several modifications (this code sample is limited to modified code):

(...)

    protected override int VisualChildrenCount => ShowContent && Content != null ? 1 : 0;

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (Content != null && ShowContent)
            Content.Arrange(new Rect(finalSize));
        return finalSize;
    }

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index > VisualChildrenCount - 1)
            throw new ArgumentOutOfRangeException(nameof(index));
        return Content;
    }

    private void LoadContent()
    {
        if (Content == null && Template != null)
            Content = (UIElement)Template.LoadContent();
        if (Content != null)
        {
            AddLogicalChild(Content);
            AddVisualChild(Content);
        }
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var desiredSize = new Size();
        if (Content != null && ShowContent)
        {
            Content.Measure(constraint);
            desiredSize = Content.DesiredSize;
        }
        return desiredSize;
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property == ShowContentProperty)
        {
            if (ShowContent)
                LoadContent();
            else
                UnloadContent();
        }
        else if (e.Property == TemplateProperty)
        {
            UnloadContent();
            Content = null;
            if (ShowContent)
                LoadContent();
        }
    }

(...)

用法示例:

<StackPanel>
    <CheckBox x:Name="CB" Content="Show content" />
    <local:ConditionalContentControl ShowContent="{Binding ElementName=CB, Path=IsChecked}">
        <ControlTemplate>
            <Border Background="Red" Height="200" />
        </ControlTemplate>
    </local:ConditionalContentControl>
</StackPanel>

这篇关于XAML中的动态内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆