如果我想根据条件显示某些内容,那么简单的方法就是使用可见性绑定:
<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 masters)已经解决了这个问题。
答案 0 :(得分:1)
我能想到的最重用的解决方案是创建自定义控件并将其内容包装在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 :(得分:0)
如果您不介意在解析 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>
注意,这种方法有其缺点,例如:如果未立即加载内容,则与相关源的绑定将报告错误。
答案 2 :(得分:0)
我决定将我的尝试作为答案发布:
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;
}
简短,明确(是吗?)并且正在工作。
我们的想法是在ContentControl.Content
或Template
时使用ShowContent
指定控件模板并将控件Content
更改为显示/隐藏(支持设计时间)值已经改变。
测试示例(包括相对和名称绑定):
<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>
要了解它的延期:
public class MyCheckBox : CheckBox
{
public MyCheckBox()
{
Debug.WriteLine("MyCheckBox is constructed");
}
}