# .NET and More > WPF, WCF, WF >  [WPF] UserControl - change appearance on mouse-over

## NickThissen

Hi,

I am creating a simple Calendar control (such as the Outlook calendar in Month view) where I can place appointments on days.
The control consists of a UserControl called Calendar with a Grid. During run-time, when the control knows which month to show, this Grid is filled with a number of DayControls. Each DayControl is another UserControl with a header (showing the day) and a ListBox (where the appointments should be shown, this is not implemented yet).

At the moment it looks like this:



What I want is pretty simple: when the mouse is over a certain DayControl, I want the header (this is a StackPanel), the ListBox and the Border around them to both get a different appearance (a golden selected appearance). 

I am certain I need to use styles and triggers, but I haven't figured it out properly yet after trying for at least 3 hours... 


The code for my DayControl UserControl is this:

xml Code:
<UserControl x:Class="CalendarControlLibrary.Controls.DayControl"
             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:Controls="clr-namespace:CalendarControlLibrary.Controls" mc:Ignorable="d" 
             d:DesignHeight="150" d:DesignWidth="150">
     <UserControl.Resources>
        <!-- Colors -->
        <Color x:Key="BlueGradientTop">#A5BFE1</Color>
        <Color x:Key="BlueGradientBottom">#E6EDF7</Color>
        <Color x:Key="GoldGradientTop">#FFB86D</Color>
        <Color x:Key="GoldGradientBottom">#FFE876</Color>
        <Color x:Key="LightBlueGradientTop">#FFFFFF</Color>
        <Color x:Key="LightBlueGradientBottom">#AAE6EDF7</Color>
        <Color x:Key="DarkBlueBorder">#8DAED9</Color>
         <!-- Brushes -->
        <LinearGradientBrush x:Key="HeaderBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Offset="0" Color="{StaticResource BlueGradientTop}" />
            <GradientStop Offset="1" Color="{StaticResource BlueGradientBottom}" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="HeaderSelectedBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Offset="0" Color="{StaticResource GoldGradientTop}" />
            <GradientStop Offset="1" Color="{StaticResource GoldGradientBottom}" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="ContentBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Offset="0" Color="{StaticResource LightBlueGradientTop}" />
            <GradientStop Offset="1" Color="{StaticResource LightBlueGradientBottom}" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="ContentSelectedBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
            <GradientStop Offset="0" Color="{StaticResource GoldGradientTop}" />
            <GradientStop Offset="1" Color="{StaticResource GoldGradientBottom}" />
        </LinearGradientBrush>
        <SolidColorBrush x:Key="ControlBorderBrush" Color="{StaticResource DarkBlueBorder}" />
        <SolidColorBrush x:Key="ControlSelectedBorderBrush" Color="{StaticResource GoldGradientBottom}" />
    </UserControl.Resources>
     <!-- The border around the Header and Listbox -->
    <Border CornerRadius="2" BorderThickness="2">
    
        <!-- The border style -->
        <Border.Style>
            <Style TargetType="Border">
                <Setter Property="BorderBrush" Value="{DynamicResource ControlBorderBrush}" />
                <Style.Triggers>
                    <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">
                        <Setter Property="BorderBrush" Value="{DynamicResource ControlSelectedBorderBrush}" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Border.Style>
         <!-- The Grid that contains the Header StackPanel and Content ListBox -->
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
             <!-- The Header StackPanel -->
            <StackPanel x:Name="headerPanel" Grid.Row="0">
                <TextBlock x:Name="dayLabel" Text="{Binding Path=DayNumber, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" 
                           Margin="3, 0, 0, 2"/>
                 <StackPanel.Style>
                    <Style TargetType="StackPanel">
                        <Setter Property="Background" Value="{DynamicResource HeaderBackgroundBrush}" />
                        <Style.Triggers>
                            <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">
                                <Setter Property="Background" Value="{DynamicResource HeaderSelectedBackgroundBrush}" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </StackPanel.Style>
            </StackPanel>
             <!-- The Content ListBox -->
            <ListBox Grid.Row="1" x:Name="contentPanel">
                <ListBox.Style>
                    <Style TargetType="ListBox">
                        <Setter Property="BorderBrush" Value="Transparent" />
                        <Setter Property="Background" Value="{DynamicResource ContentBackgroundBrush}" />
                        <Style.Triggers>
                            <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">
                                <Setter Property="Background" Value="{DynamicResource ContentSelectedBackgroundBrush}" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </ListBox.Style>
            </ListBox>
        </Grid>
    </Border>
 </UserControl>

There is a couple of brushes and colors in the Resources, and my goal is to simply swap the normal brushes out for the selected ones when the mouse enters the DayControl.

In detail:
The StackPanel (header) should go from HeaderBackgroundBrush to HeaderSelectedBackgroundBrushThe ListBox Background should go from ContentBackgroundBrush to ContentSelectedBackgroundBrushThe BorderBrush of the Border (root control) should go from ControlBorderBrush to ControlSelectedBorderBrush

I am trying to do this using a Style for each of these three controls with a Trigger that should run when the mouse is over the current DayControl, so I am testing for the property Controls :Big Grin: ayControl.IsMouseOver. (The "Controls:" part is because it is in the Controls namespace).


It doesn't work completely right. For some reason, the Border seems to swap out fine; when my mouse is anywhere over a certain DayControl, the border turns gold. 

The header and ListBox don't work though; they only swap out their backgrounds when the mouse is over THEMSELVES. When the mouse is over the header StackPanel, the header becomes gold, but the ListBox remains the default (right image). In the same way, when the mouse is over the ListBox, the ListBox becomes gold, but the header StackPanel now is the default blue again (left image)...


I don't get it; as far as I can see I am using the exact same method on all three controls, but only the border works correctly. 

Is it a flaw in my design (is the IsMouseOver property false when the mouse is over a StackPanel, but not when it is over a Border???  :Ehh: ), a typo somewhere, or something completely different...?

Also, is there no way to put the three setters into a single trigger, so that I don't have to create a style for each control? The trigger is the same for all styles so that sounds more logical, but I couldn't figure it out...

Thanks!

----------


## Evil_Giraffe

xml Code:
<!-- The Content ListBox -->
<ListBox Grid.Row="1" x:Name="contentPanel">
....
            <Style.Triggers>
                <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">
...

This isn't doing what you think it is.  This is not referring to the IsMouseOver property on your instance of DayControl.  It is referring to the dependency property DayControl.IsMouseOver on your ListBox control.

That DependencyProperty is the one inherited from from FrameworkElement (or perhaps Control?  At least, from a very primitive type in the hierarchy).  As such, the IsMouseOver property on all controls _are the same DependencyProperty_ which is why the above works.

(If you've not grokked exactly what a DP is, basically think of it as a key into a dictionary that holds all the the values of a DependencyObject's DependencyProperties.  Thus you use the same DP to access the different values of IsMouseOver).

So, the trigger is firing when the mouse is over, not the DayControl, but the ListBox.  Likewise with the Border, but because the Border takes up the whole of the DayControl, this one works how you expect.

The way to get the behaviour you want is actually what you wanted to do in the first place: have several backgrounds set by the same trigger.  This is just a simple addition to the setter:

I couldn't get your XAML to show up in a demo app, but the Border's style should look something like this - note the use of the TargetName attribute in the Setters:


xml Code:
<!-- The border style -->
<Border.Style>
    <Style TargetType="Border">
        <Setter Property="BorderBrush" Value="{DynamicResource ControlBorderBrush}" />
        <Style.Triggers>
            <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">
                <Setter Property="BorderBrush" Value="{DynamicResource ControlSelectedBorderBrush}" />
                <Setter TargetName="headerBrush" Property="Background" Value="{DynamicResource HeaderSelectedBackgroundBrush}" />
                <Setter TargetName="contentPanel" Property="Background" Value="{DynamicResource ContentSelectedBackgroundBrush}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</Border.Style>

----------


## Evil_Giraffe

As an additional note, are you aware that for what you're trying to do, you don't need to use DynamicResources?  StaticResouces will work just fine for this - you'd only need DynamicResources if the brushes themselves might change during the execution of you application and you want to automatically pick up those changes.  Just swapping the brushes in and out doesn't require it.

----------


## NickThissen

Thanks.

I tried what you said, but it doesn't seem to work. I am getting a compile error: "Targetname property cannot be set on a Style Setter."

Any idea why?  I didn't change anything; I just added the trigger to the Triggers of the Border style as you instructed.


Also, about DynamicResource vs StaticResource... I have this control in a separate project then the one I'm using it in (like a control library project). I noticed if I used StaticResources that the brushes weren't being picked up, it didn't show any styles I set. Then I changed to DynamicResource and it worked...?

----------


## Evil_Giraffe

Whoops!!!! Sorry, I'm always forgetting that.  You want to place the setters in a Triggers collection on the Border:


xml Code:
<Border.Triggers>    <Trigger Property="Controls:DayControl.IsMouseOver" Value="True">        <Setter Property="Border.BorderBrush" Value="{DynamicResource ControlSelectedBorderBrush}" />        <Setter TargetName="headerBrush" Property="Control.Background" Value="{DynamicResource HeaderSelectedBackgroundBrush}" />        <Setter TargetName="contentPanel" Property="Control.Background" Value="{DynamicResource ContentSelectedBackgroundBrush}" />    </Trigger></Border.Triggers>

Note that because we're not a Style that has a TargetType defined, we need to qualify the properties we are specifying.


As to your resources, there is probably something funny about the way you've got the styles imported if StaticResource isn't working, but I wouldn't worry too much about it if DynamicResource is working fine.

----------


## NickThissen

That doesn't work either... I get a runtime exception "Triggers collection members must be of type EventTrigger." I suppose I could use event triggers, but a normal trigger sounds easier?


I restarted from scratch recently actually, I am now doing things a bit more like you told me. The individual days are no longer actual UserControls but simply a class DayElement (they are only called 'Element because I already had a class Day, they are not elements). I bind a ListView with a custom view to a list of DayElements and their style is now done in XAML.

xml Code:
<ListView Grid.Row="2" x:Name="daysList" ItemsSource="{Binding Path=Days, UpdateSourceTrigger=PropertyChanged}">            <ListView.View>                <views:MonthView>                    <views:MonthView.ItemTemplate>                        <DataTemplate>                            <Border BorderThickness="1" CornerRadius="0" Margin="1">                                <Border.Style>                                    <Style>                                        <Setter Property="Control.Background" Value="{DynamicResource DayHeaderBackgroundBrush}" />                                        <Setter Property="Border.BorderBrush" Value="{DynamicResource DayBorderBrush}" />                                                                            </Style>                                </Border.Style>                                 <!-- This does not work, I need EventTriggers apparently -->                                <Border.Triggers>                                        <Trigger Property="Border.IsMouseOver" Value="True">                                            <Setter TargetName="headerPanel" Property="Control.Background" Value="{DynamicResource SelectedDayHeaderBackgroundBrush}" />                                            <Setter Property="Border.BorderBrush" Value="{DynamicResource SelectedDayBorderBrush}" />                                        </Trigger>                                </Border.Triggers>                                 <Grid>                                    <Grid.RowDefinitions>                                        <RowDefinition Height="Auto" />                                        <RowDefinition Height="*" />                                    </Grid.RowDefinitions>                                                                    <Border x:Name="headerPanel" Grid.Row="0" Background="AliceBlue" BorderBrush="DarkBlue" BorderThickness="1">                                        <TextBlock Text="{Binding DisplayDate}" Margin="5,0" />                                    </Border>                                                                       <ListBox Grid.Row="1" />                                </Grid>                             </Border>                        </DataTemplate>                    </views:MonthView.ItemTemplate>                </views:MonthView>            </ListView.View>        </ListView>

The code for DayElement is just this:

csharp Code:
public class DayElement : NotifyObject    {        public DayElement(MonthCalendar calendar, DateTime date)        {            _Calendar = calendar;            this.Date = date.Date;        }         private readonly MonthCalendar _Calendar;        public MonthCalendar Calendar { get { return _Calendar; } }         private DateTime _Date;        public DateTime Date        {            get { return _Date; }            set            {                _Date = value;                this.OnPropertyChanged(() => this.Date);                this.OnPropertyChanged(() => this.BelongsToCurrentMonth);            }        }         internal bool ShowMonthName        {            get { return this.Date.Day == 1 || this.Calendar.Days.IndexOf(this) == 0; }        }         internal bool BelongsToCurrentMonth        {            get { return (this.Calendar.DisplayedMonth.Month == this.Date.Month); }        }         public string DisplayDate        {            get            {                if (this.ShowMonthName)                    return string.Format("{0} {1}", ((DateHelpers.Month)this.Date.Month).ShortName, this.Date.Day);                return this.Date.Day.ToString();            }        }    }

It works fine, just not the highlighting on mouse over yet. 

Another question I had is related to the 'BelongsToCurrentMonth' property. It indicates whether this day is in the month selected by the user. Think Outlook calendar: when you select June 2011 for example (assuming your first day of the week is sunday) then there are still 3 days in May on the calendar, to fill up the first sunday, monday and tuesday that happen to lie in the previous month (same for the last few days which lie in july). In Outlook, those days get a different background to indicate that they are in a different month. 

How do I apply a style based on this property? I have two brushes ready, one to use if BelongsToCurrentMonth is true, the other when it is false. I just have no way to set the style based on this property... I tried using a trigger, but apparently BelongsToCurrentMonth has to be a DP for that to work. I can't make it a DP since DayElement is not a DependencyObject (so does not have GetValue/SetValue methods). 

How does this work?

----------


## Evil_Giraffe

Hmm, sorry, I thought I had checked those triggers worked correctly.

There are two options for your trigger dilemma:

1) Keep the DayControl and use that in the DataTemplate for your DayElement, but place a DependencyProperty on that control that you bind IsInCurrentMonth to.

Then you can trigger based on _that_ DP.

2) Use a DataTrigger that uses a Binding rather than a DP.







> In Outlook, those days get a different background to indicate that they are in a different month.


I was wondering whether you were going to implement this  :Smilie:  Will look spiffy if you get it working.

----------

