Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create UI elements lazily in WPF?

We are creating a WPF application which makes heavy use of highly decorated input elements. A simple example of decorated element is a TextBox which looks like a read-only TextBlock when it doesn't have focus, and turns into a TextBox after receiving focus. Also, additional visual feedback is provided when the changed value is being saved to database. The problem is that showing a view that contains lots of these elements (let's say 100) is very slow and makes application very unresponsive.

We have implemented this decorator as UserControl which contains all required elements (for example the TextBlock to show unfocused text and rotating image for busy indicator). Then we add the input element as child of this decorator control, meaning that in addition to all extra elements, the decorator also contains the input element in its visual tree. In XAML this would look like:

<custom:Decorator Context="{Binding ValueHelper}" >
    <TextBox Text="{Binding ValueHelper.Text}"/>
</custom:Decorator>

This makes it easy for us to decorate any input element we want, be it either text box, date picker, combobox or any custom element.

Now back to the problem: let's say we have a view which contains 100 decorated text boxes and we navigate to that view. What happens? At least my quad-core laptop freezes for a long time because it has to create many hundred text blocks, rectangles, images etc. to provide the visual feedback for every decorated element, although no decorations are visible yet. What really would be required is only 100 TextBlocks because that's what is visible on the screen. It is only after element receives mouse over event or focus when other elements are needed. Also, only one element is being edited at a time, so only one input element (in this case, textbox) would be enough for the whole application.

So, what would be the best way to achieve same decorations without creating all decorating elements (or the actual input element) for every element in the view?


An example of decorated TextBox to clarify use-case:

The text box looks like a read-only TextBlock when it does not have focus or mouse cursor is not currently on top of it (state 1). Also, three dots ("...") are shown because element does not currenly have any value.

When mouse cursor is moved on top of the element, a dotted green rectangle appears around TextBlock to indicate that element can be modified (state 2). The color would be red if TextBox happened to be read-only.

After receiving focus element turns into actual TextBox which can be used to modify the actual value (state 3).

The value is stored into database after textbox loses its focus, and to show that the value is currently being saved a busy indicator appears to left side of element (state 4).

Finally, the value has been saved and element returns to its idle state showing the new value (state 5). (Actually the elements have even more states related to validation and other specific requirements but you certainly got the point that elements really are highly decorated.)

enter image description here

like image 216
user544511 Avatar asked Mar 27 '13 12:03

user544511


1 Answers

Instead of drawing all the UI elements upfront, only draw the ones needed

WPF allows you to modify the Template of an object using a DataTrigger, so you can adjust the template for your Decorator based on your triggers

An example style for your Decorator might look something like this:

<Style TargetType="{x:Type ContentControl}">
    <!-- Default Template -->
    <Setter Property="ContentTemplate" 
            Value="{StaticResource NoDecoratorTemplate}" />

    <Style.Triggers>
        <DataTrigger Property="Text" Value="">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource BlankTemplate}" />
        </DataTrigger>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource MouseOverTemplate}" />
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource FocusedTemplate}" />
        </Trigger>
        <DataTrigger Property="{Binding IsLoading}" Value="True">
            <Setter Property="ContentTemplate" 
                    Value="{StaticResource LoadingTemplate}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Also, WPF shouldn't load non-visible items. You can try setting your decorators Visibility="Collapsed" as a test to see if that fixes load time.

If it does and you don't want to go the Template route, you could ensure that the Visibility of all your objects is set to Collapsed to begin with, and they only get set to Visible when they should be displayed.

If it doesn't fix your load time, then your problem is probably somewhere else. For example, it looks like some of your decorator items are sized according to the content control placed inside of it, so it's possible what's actually slowing you down isn't displaying the objects, but determining the size of the objects.

like image 89
Rachel Avatar answered Oct 04 '22 06:10

Rachel