Solved

Property & Style to apply to WPF controls to make them collapsible.

Posted on 2010-08-13
8
1,268 Views
Last Modified: 2013-12-17
I've created a series of controls that can be collapsed and expanded by clicking a little button above the control. My problem now is I feel as though I've copy pasted a lot of code and their should be a way to apply the same thing to each of these controls without so much code replication.

I've gone a certain distance by creating a style for the button, and for the collapsible control that responds to the button, and applying those styles to each page that uses them - but each page still requires it's own copy of the Property "isCollapsed" in its codebehind, as well as an event handler for the button that collapses and expands it.

I was hoping a veteran of WPF might find a way that this property and event handler can also somehow be handled in the style so that no codebehind is required at all.


This is what code the pages using collapsible regions have (It may be useful to open up the image below to follow along with the xaml):
    <Grid>
        ...
        <Grid ...>
            ...
            <!-- The Expand / Collapse button and header for this subsection  -->
            <Border Grid.Column="0" MouseDown="Expand_MouseDown" Style="{StaticResource CollapseLinkBorder}">
                <TextBlock Style="{StaticResource CollapseLink}" />
            </Border>
            <!-- The control title -->
            <TextBlock Grid.Column="2" Style="{StaticResource ControlSectionHeader}">Existing Exposure Sets</TextBlock>
        </Grid>
        
        <!-- This is the panel that will be shown or hidden based on whether the control is collapsed -->
        <DockPanel ... Style="{StaticResource CollapsibleRegion}">
        ...
        </DockPanel>
    </Grid>

Open in new window


Note that there's a styled border containing a styled button which has a mousedown event. This is the same for everything that uses the control. There's then the collapsible area, which might be a Grid, DockPanel, CustomPage, ... any UIElement.

This is the code that each page must have in the codebehind to support the IsCollapsed property:
    public partial class ExistingExposureSets:UserControl, INotifyPropertyChanged
    {
        bool isCollapsed;
        public bool IsCollapsed
        {
            get { return isCollapsed; }
            set
            {
                isCollapsed = value;
                NotifyPropertyChanged("IsCollapsed");
            }
        }

        public ExistingExposureSets()
        {
            this.DataContext = this;
            isCollapsed = false;
            InitializeComponent();
        }

        private void Expand_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            IsCollapsed = IsCollapsed ^ true;
        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String propertyName)
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

    }

Open in new window


Ideally, all of this code (except the constructor) could be removed completely.

Finally, here are the styles responsible for making it look the way it does, and actually work:
    <!-- The next three are used on all controls with a collapsible region -->
    <Style TargetType="{x:Type TextBlock}" x:Key="CollapseLink">
        <Setter Property="FontWeight" Value="Bold" />
        <Setter Property="Margin" Value="4,0,4,0" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="Text" Value="-" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding ElementName=thisControl, Path=IsCollapsed}" Value="true">
                <Setter Property="Text" Value="+" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
    
    <Style TargetType="{x:Type Border}" x:Key="CollapseLinkBorder">
        <Setter Property="CornerRadius" Value="4,4,4,4" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="Black" />
        <Setter Property="Width" Value="16" />
        <Setter Property="Cursor" Value="Hand" />
        <Setter Property="Background" Value="{StaticResource CollapseButtonBackground}" />
    </Style>

    <Style x:Key="CollapsibleRegion">
        <Setter Property="UIElement.Visibility" Value="Visible" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding ElementName=thisControl, Path=IsCollapsed}" Value="true">
                <Setter Property="UIElement.Visibility" Value="Collapsed" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

Open in new window

As you can see, even getting this much code into a style required a hack - I can only bind to the correct IsCollapsed property by naming all of the pages containing this style "thisControl". I suppose this would be cleaner if I bound to the first ancestor of type "Page" but I couldn't figure that one out.

I appreciate any advice on reducing the amount of duplicate code in controls that use this feature as much as possible.

For instance, if some sort of Attached Property or Dependency Property (which I know very little about) could be defined in the style xaml, then I would be in the clear. Mostly, I want to avoid having to have an IsCollapsed property defined for every collapsible region on my UI.

Thank you!

Alain
ExpandCollapse.PNG
0
Comment
Question by:alainbryden
  • 4
  • 2
8 Comments
 
LVL 25

Expert Comment

by:apeter
Comment Utility
Can't you have ExistingExposureSets class has base class for all of your XAML which requires collapsable property. So that the codebehind is inherited automatically and your implementation is at one place ?
0
 
LVL 20

Author Comment

by:alainbryden
Comment Utility
Hmm, that is a good idea. Every class already inherits a base class though, and only some pages additionally need this iscollapsed property. Is multiple inheritance supported in C#?
0
 
LVL 25

Accepted Solution

by:
apeter earned 250 total points
Comment Utility
Multiple inheritance is not supported but you can one more "multi level" inheritence class in between.
0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 20

Author Comment

by:alainbryden
Comment Utility
Ugh, I tried my best to create a class CollapsibleControl that has UserControl as its base class, but then when I try to create a xaml file that uses CollapsibleControl instead of UserControl as the root element, everything goes to hell. I don't think changing the base class is going to solve this one :/

--
Alain
using System;
using System.Windows.Controls;
using System.ComponentModel;

namespace QuartzShareUI.InterfaceComponents.Shared
{
    public abstract class CollapsibleControl:UserControl, INotifyPropertyChanged
    {
        bool isCollapsed;
        public bool IsCollapsed
        {
            get { return isCollapsed; }
            set
            {
                isCollapsed = value;
                NotifyPropertyChanged("IsCollapsed");
            }
        }

        public CollapsibleControl()
        {
            isCollapsed = false;
        }

        private void Expand_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            IsCollapsed = IsCollapsed ^ true;
        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String propertyName)
        {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

Open in new window

0
 
LVL 20

Author Comment

by:alainbryden
Comment Utility
Figured this out just now.

The trick is to use a style toggle button, and use it's IsChecked property in place of having to create an IsCollapsed property:

[code]
    <Style TargetType="{x:Type ToggleButton}" x:Key="CollapseLink">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border CornerRadius="4,4,4,4" BorderBrush="Black" BorderThickness="1"
                            Width="16" Cursor="Hand" Background="{StaticResource CollapseButtonBackground}">
                        <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="FontWeight" Value="Bold" />
        <Setter Property="Content" Value="-" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding ElementName=IsCollapsed, Path=IsChecked}" Value="true">
                <Setter Property="Content" Value="+" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <Style x:Key="CollapsibleRegion">
        <Setter Property="UIElement.Visibility" Value="Visible" />
        <Style.Triggers>
            <DataTrigger Binding="{Binding ElementName=IsCollapsed, Path=IsChecked}" Value="true">
                <Setter Property="UIElement.Visibility" Value="Collapsed" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
[/code]

Now all we need is to place one of these toggle buttons in the document, being sure to name it "IsCollapsed", and apply the collapsible region style to the main content's root element, and we can get rid of all those properties and event triggers in the code behind, as well as naming the control "thisControl"

[code]
        <Grid >
            ...
            <!-- The Expand / Collapse button and header for this subsection  -->
            <ToggleButton Grid.Column="0" x:Name="IsCollapsed" Style="{StaticResource CollapseLink}" />
            <!-- The control title -->
            <TextBlock Grid.Column="2" Style="{StaticResource ControlSectionHeader}">Conversions</TextBlock>
        </Grid>

        <!-- This contains the main content for the conversion section -->
        <Grid Grid.Row="1" Margin="4,4,0,0" Style="{StaticResource CollapsibleRegion}">
        ...
[/code]

--
Alain
0
 
LVL 27

Assisted Solution

by:MikeToole
MikeToole earned 250 total points
Comment Utility
In WPF the Expander control does this already:

        <Expander   Header="An Expanding Region" ExpandDirection="Down">
            <StackPanel>
                <TextBlock>One</TextBlock>
                <TextBlock>Two</TextBlock>
                <TextBlock>Three</TextBlock>
            </StackPanel>
        </Expander>

It defaults to an up/down arrow for the expand?collapse button, but I guess that could be styled
0
 
LVL 20

Author Comment

by:alainbryden
Comment Utility
Well, the solution was my last post (33445984) but what counts is the points for assistance.

--
Alain
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Suggested Solutions

Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now