Dynamic content in xaml

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

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

With this approach, a visual tree is still created and can cause performance problems if it Somethinghas a complex structure (many children, bindings, events, triggers, etc.).


The best approach is to add content through a 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>

But what a nightmare, agree? Having several of these dynamic components will pollute xaml and make navigation difficult.

Is there another way?


, , Type , . , , , meh. , -, xaml, .

, , . xaml- , , . , : 1) ;) 2) , . 3) , ( xaml) .

0
3

, , - 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>
+1

, 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 = DependencyProperty.Register(
        nameof(ShowContent),
        typeof(bool),
        typeof(ConditionalContentControl),
        new FrameworkPropertyMetadata
        {
            AffectsArrange = true,
            AffectsMeasure = true,
            DefaultValue = false,
        });

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

    #endregion
}

:

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

, , . , .

0

I decided to post my attempt as an answer:

public class DynamicContent : ContentControl
{
    public bool ShowContent
    {
        get { return (bool)GetValue(ShowContentProperty); }
        set { SetValue(ShowContentProperty, value); }
    }
    public static readonly DependencyProperty ShowContentProperty =
        DependencyProperty.Register("ShowContent", typeof(bool), typeof(DynamicContent),
        new PropertyMetadata(false,
            (sender, e) => ((DynamicContent)sender).ChangeContent((bool)e.NewValue)));

    protected override void OnContentChanged(object oldContent, object newContent)
    {
        base.OnContentChanged(oldContent, newContent);
        ChangeContent(ShowContent);
    }

    void ChangeContent(bool show) => Template = show ? (ControlTemplate)Content : null;
}

It is short, clear (is it?) And works.

The idea is to use ContentControl.Contentchange control to indicate the pattern Templateto show / hide when the value ShowContentor Content(to support development time) has changed.

Testing example (including relative and by name):

<StackPanel Tag="Test">
    <CheckBox x:Name="comboBox"
              Content="Show something"
              IsChecked="{Binding ShowSomething}" />
    <local:DynamicContent ShowContent="{Binding IsChecked, ElementName=comboBox}">
        <ControlTemplate>
            <local:MyCheckBox IsChecked="{Binding IsChecked, ElementName=comboBox}"
                      Content="{Binding Tag, RelativeSource={RelativeSource AncestorType=StackPanel}}" />
        </ControlTemplate>
    </local:DynamicContent>
</StackPanel>

To find out what he put off:

public class MyCheckBox : CheckBox
{
    public MyCheckBox()
    {
        Debug.WriteLine("MyCheckBox is constructed");
    }
}
0
source

Source: https://habr.com/ru/post/1693992/


All Articles