xaml中的动态内容

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了xaml中的动态内容相关的知识,希望对你有一定的参考价值。

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

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

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


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

<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并使其难以导航。

还有另外一种方法吗?


我随时都在使用数据模板,但是当动态部分仅依赖于属性值时,创建专用的Type并实际定义数据模板太多了。当然,该属性可以重构为一个类型,然后可以使用自己的数据模板,但是meh。我真的更喜欢不每次都这样做,在xaml中定义的太多小型和实际数据 - 太阳镜对我来说同样糟糕。

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

答案

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

这是一个示例实现:

[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)大小。为了在不应该显示时从可视树中卸载内容,我们需要进行一些修改(此代码示例仅限于修改后的代码):

(...)

    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时实例化内容并且只想将其保留在可视树之外,那么这是一个实现此目标的控件:

[ContentProperty(nameof(Content))]
public class ConditionalContentControl : FrameworkElement
{
    private UIElement _Content;
    public UIElement Content
    {
        get => _Content;
        set
        {
            if (ReferenceEquals(value, _Content)) return;
            UnloadContent();
            _Content = value;
            if (ShowContent)
                LoadContent();
        }
    }

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

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

    #region Dependency properties

    public static readonly DependencyProperty ShowContentProperty = DependencyPrope

以上是关于xaml中的动态内容的主要内容,如果未能解决你的问题,请参考以下文章

是否可以动态编译和执行 C# 代码片段?

WPF 获取xaml

RecyclerView holder中的Android Google Maps动态片段

WPF应用程序中,调用用户控件时,可以访问到在这个用户控件Xaml代码中的所有内容,这样岂不是不安全?

Xamarin表单ViewCell Xaml片段

在后台代码中引入XAML的方法