48780

Dynamic content in xaml

Question:

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.).

<hr />

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>

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

<strong>Is there another way?</strong>

<hr />

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 <em>small types</em> and actual data-temples defined in xaml sounds same bad to me.

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 <em>the best</em> way 3) I am sure others (xaml masters) have this problem solved already.

Answer1:

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.

Here's an example implementation:

[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 }

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

Usage example:

<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>

Answer2:

If you don't mind the content being instantiated upon parsing the <em>XAML</em> and only want to keep it out of visual tree, here's a control that accomplishes this goal:

[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 }

Usage:

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

Note though that this approach has its drawbacks, e.g. bindings with relative sources will report errors if the content is not loaded immediately.

Answer3:

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's short, clear (is it?) and working.

The idea is to use ContentControl.Content to specify control template and change control Template to <em>show/hide</em> it when ShowContent or Content (to support design time) value is changed.

Testing example (including relative and by name bindings):

<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 see what it's deferred:

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

Recommend

  • Java FastXML JSON library: How to parse a nested JSON structure
  • SQL Server 2008 XML Empty Node
  • Parser Error in ASP.NET Web Application
  • Import CSV with headers, scan for log on remote servers, add data to array, export array to CSV
  • Apply PivotItemHeader style to PivotItem in UWP
  • Singleton Alternative - is it equivalent?
  • Polymer build not to create bundled and unbundled folder
  • Having an issue with my TextBox control template
  • How to fallback to entirely different index page if user has javascript disable?
  • How do you SELECT several columns with one distinct column
  • Best HTML5 structure for a layout where the title/header is outside the article tag
  • How to make SASS put relative paths in its output
  • How to select table rows/complete table?
  • Multicolor tooltip in Qt
  • What's wrong with my datatrigger binding?
  • Debugging VB6 Code From Visual Studio 2010
  • Low TTL with Leveled Compaction, should I reduce gc_grace_seconds to improve read performance withou
  • How to add a focus style to an editable ComboBox in WPF
  • How can I sort a a table with VBA with given text condition?
  • NHibernate Validation Localization with S#arp Architecture
  • How to delay loading a property with linq to sql external mapping?
  • Ajax Loaded meta Tags
  • Xamarin Forms - UWP Fonts
  • QLineEdit password safety
  • Nant, Vault & Windows Integrated Authentication
  • Bug in WPF DataGrid
  • Join two tables and save into third-sql
  • Arrow is showed instead of the material design version hamburger icon. Why doesn't syncState in
  • How to model a transition system with SPIN
  • Redux, normalised entities and lodash merge
  • ORA-29908: missing primary invocation for ancillary operator
  • jQuery tmpl and DataLink beta
  • How can I estimate amount of memory left with calling System.gc()?
  • Arrays break string types in Julia
  • File upload with ng-file-upload throwing error
  • Python: how to group similar lists together in a list of lists?
  • Free memory of cv::Mat loaded using FileStorage API
  • how does django model after text[] in postgresql [duplicate]
  • Java static initializers and reflection
  • Converting MP3 duration time