-3

I've made some progress writing a user control which doesn't just hide content, but completely removes it, if some condition isn't met.

a couple example use cases:

<mynamespace:If Condition="{Binding PerformingWork}">
    <LoadingThrobber />
</mynamespace:If>
<mynamespace:If Condition="{Binding HasResults}">
    <ItemsControl ...>
        ...
    </ItemsControl>
</mynamespace:If>

or

<mynamespace:If Condition="{Binding UserHasMorePermissions}">
    ...
</mynamespace:If>

etc.

my solution APPEARS to work, but has a serious downside: the contents of the If "block" are still loaded once, before being removed (easily proved by putting a MessageBox.Show(...) in some child control's constructor). I want to prevent the content from running AT ALL, in case it's trying to do some heavy work, or operate on data that doesn't yet exist, etc.

here's what I've come up with so far:

    public partial class If : UserControl
    {
        private object ConditionalContent { get; set; }

        public If()
        {
            InitializeComponent();

            Loaded += OnLoad;
        }

        private void OnLoad(object sender, RoutedEventArgs e)
        {
            ConditionalContent = Content;
            Loaded -= OnLoad;
            OnConditionChanged();
        }

        private void OnConditionChanged()
        {
            Content = Condition ? ConditionalContent : null;
        }

        // === component properties

        // omg, WPF
        public bool Condition
        {
            get { return (bool)GetValue(ConditionProperty); }
            set { SetValue(ConditionProperty, value); }
        }

        public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register("Condition", typeof(bool), typeof(If), new PropertyMetadata(OnConditionChangedCallback));
        
        private static void OnConditionChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is If s)
            {
                s.OnConditionChanged();
            }
        }
    }

the XAML is basically empty; I'm including it juuuust in case there's something important here:

<UserControl x:Class="XamlExtensions.If"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:XamlExtensions"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
</UserControl>

of course, if there's another way to accomplish the same goal, that'd be great.

Ben
  • 719
  • 6
  • 25
  • Does this answer your question? [How to Create Custom MarkupExtension like StaticResource?](https://stackoverflow.com/questions/23372411/how-to-create-custom-markupextension-like-staticresource) – Sinatr Jul 23 '21 at 13:17
  • Markup extension seems to fit. Or you could subclass control and manipulate its control template, making it `null` when condition evaluates to `false`. – Sinatr Jul 23 '21 at 13:18
  • A more general approach to show/hide something is to manipulate visibility or control template using [data trigger](https://stackoverflow.com/a/1422632/1997232). You could also just bind condition to `Visibility` using [converter](https://stackoverflow.com/a/7000922/1997232). So many ways. – Sinatr Jul 23 '21 at 13:19
  • That's not a good idea. You will bloat your visual tree with redundant user controls. This downgrades your application's performance, especially when it comes to rendering. You should make use of [triggers](https://docs.microsoft.com/en-us/dotnet/api/system.windows.style.triggers?view=net-5.0#examples), styles and templates. Binding converters are also an option (as already stated): [How to: Convert Bound Data](https://docs.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8). You simply have to toggle `UIElement.Visibility` using a trigger. – BionicCode Jul 23 '21 at 13:52
  • Could you design the screen in such way, where you can utilise content control or data templates? – XAMlMAX Jul 23 '21 at 13:56
  • @BionicCode Visibility doesn't prevent the content from loading/executing its code. I would expect to downgrade my application's performance _more_ by loading and executing content I don't want... but maybe I'm wrong to expect that? I'll look into custom markupextentions; I was not aware of those – Ben Jul 23 '21 at 15:05
  • If a control is collapsee then it's not in the visual tree. I'd probably bind visibility. – Andy Jul 23 '21 at 16:48
  • @Andy Visibility doesn't prevent the code from executing. if the children controls involve doing some heavy work, they shouldn't be merely hidden. I think Efraim's answer is great, I'm just having trouble with some WPF details that are probably my own fault. I'm going to mark it correct. – Ben Jul 23 '21 at 17:41
  • If you set Visibility to Visibility.Collapsed then the elemnt is completely removed from the viasual tree. When the parent control invalidates its visual e.g. due to a resizing event, then this collapsed element is not included into the rendering process. I don't understand what you mean by "executing content", but I can asure you that each element added to the visual tree will impact the rendering perfomance as rendeing occurred recursive. Layout calculations can involve complex algorithms. You should keep this in mind when designing the UI. – BionicCode Jul 23 '21 at 18:00
  • The answer is not great as it overcomplicates a simple solution to a somple problem. You find it great because it picks up your idea. Every developer who understand WPF would recommend you to collapse elements that you want to remove from the visual tree. Even hiding them is better then addiing redeundant controls, that have the only purpose to hide their content. This only shows that you have not understand the possibilities you have to implement UI logic. You got many comments that tell you how to solve it. But you apparently seem to know better than all of us. – BionicCode Jul 23 '21 at 18:03
  • You can't interact with controls that are hidden. Therefore the control won't "execute any code". It appears your problem is rooted in your misunderstanding of some concepts. – BionicCode Jul 23 '21 at 18:06
  • I added MessageBox.Show to a user control's constructor to test it out... even when hidden, the MessageBox was shown. with the accepted solution, the constructor is not called, unless/until the content is shown. – Ben Jul 23 '21 at 18:13
  • But there is still the If control in the visual tree. That's the point. You could use a simple trigger that toggles the Visibility. A trigger is basically a if-condition implementation in XAML. The fact that the XAML parser creates an instance of your control does not mean that there is any resource intensive code executed. If you have a constructor that executes longrunning and/or resource intensive code, then your programming style is very bad. A constructor is supposed to initialize the object on instance or class (static) level. It must always execute fast. Instantiation must be "cheap". – BionicCode Jul 23 '21 at 18:28
  • The instantiation of an object only costs memory wheras the recursive layout calculations costs CPU resources. A heavy visual tree can result easily in sluggish UI. Adding redundant visual elements to the visual tree is by far more expensive than creating an instance (aka allocating memory). – BionicCode Jul 23 '21 at 18:28
  • This boils down to write clean code. If you write clean code, you will not have any issue when the XAML engine instantiates your control. – BionicCode Jul 23 '21 at 18:30
  • Bioniccode explained already but to emphasise this. Visibility hidden is an option i am not suggesting. Visibility collapsed is what i suggested. Collapsed means it's not there. The accepted answer is imo a poor solution. – Andy Jul 24 '21 at 09:03

1 Answers1

1

I would use a Custom Control and not a User Control and make the default child a custom DependencyProperty and add to the control's content manually

[ContentProperty("ConditionalContent")]
public partial class If : ContentControl
{
    public If()
    {
        Loaded += OnLoad;
    }

    private void OnLoad(object sender, RoutedEventArgs e)
    {
        update();
    }

    private void update()
    {
        Content = Condition ? ConditionalContent : null;
    }

    // === component properties

    // omg, WPF
    public bool Condition
    {
        get { return (bool)GetValue(ConditionProperty); }
        set { SetValue(ConditionProperty, value); }
    }

    public static readonly DependencyProperty ConditionProperty = DependencyProperty.Register("Condition", typeof(bool), typeof(If), new PropertyMetadata(OnConditionChangedCallback));

    private static void OnConditionChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is If s)
        {
            s.update();
        }
    }



    public object ConditionalContent
    {
        get { return (object)GetValue(ConditionalContentProperty); }
        set { SetValue(ConditionalContentProperty, value); }
    }

    public static readonly DependencyProperty ConditionalContentProperty =
        DependencyProperty.Register("ConditionalContent", typeof(object), typeof(If), new PropertyMetadata(null,OnConditionChangedCallback));


}

and it works like this

<local:If Condition="{Binding}">
    sdgdfhsh <!-- or anything else! -->
</local:If>
Efraim Newman
  • 927
  • 6
  • 21
  • this is promising in that the child contents are not being rendered! woohoo! however the Condition binding seems to be confused; I get `property not found on object of type If` for whatever I bind to in the `Condition` property – Ben Jul 23 '21 at 14:46
  • I wondered if you might mean to literally use `Condition="{Binding}"`, and I was able to get that to work, kind of? this looks wrong to me, and VS throws a XAML binding failure, but the component actually works! `...` is there a way to use "SomeBoolPropertyOfDataContext" in the Condition? – Ben Jul 23 '21 at 15:20
  • the above comments are probably just my own lack of WPF knowledge; I'll google around and figure it out. your answer definitely works, or is at least 99% there. I'll propose an edit to your XAML code once I figure out what it should be. thanks for the quick answer! – Ben Jul 23 '21 at 17:42
  • I did not mean that you have to use `Binding`, I was demonstrating the it the control will update dynamically. – Efraim Newman Jul 24 '21 at 17:50