XAML中的动态内容 [英] Dynamic content in 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屋!