6

Hi I think this is a beginners questions. I've searched all the related questions. But all of them are answered by .xaml. However, what I need is the back code. I have a TabControl. I need to set the background color of its items. I need to set different colors for the items when it is selected, unselected and hover. Thank you so much for your help. I want to post my codes here. However, currently, all I have is an instance of the TabControl and one property called ActiveTabIndex.

---------------------------------------Edit 1-----------------------------------------------

I have added an event SelectionChanged

(this.myTabControl as System.Windows.Controls.TabControl).SelectionChanged += TabSet_SelectionChanged;

void TabSet_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            foreach (System.Windows.Controls.TabItem item in (this.myTabControl as System.Windows.Controls.TabControl).Items)
            {
                if (item == (this.myTabControl as System.Windows.Controls.TabControl).SelectedItem)
                {
                    item.Background = System.Windows.Media.Brushes.Red;
                }
                else
                    item.Background = System.Windows.Media.Brushes.Green;
            }
        }

However, I can only set the Green actually. The background color of the selected item keeps as the default color instead of red. Any hints about this? Also, I would like to know how to add event for the hover. Haven't find the exact event. Thanks again.

-----------------------Edit 2------------------------------

After a long long long discussion. I've decided (actually not my decision) to use the XAML. Yeah. I am new to WPF. So I still have questions about this (I am so sorry for this, Please bear me). The questions are here: I would like to change the background color to Orange when the mouse is over the TabItem. Now the color is Orange when the mouse is over the ContentPanel and TabItem. I need it to be orange when the mouse is over the TabItem only. (I am not sure I am clear enough.) Another question is that I would let users to set the color instead of setting is to red directly. I need some bindings I think. For this question, I will google later for sure. Just want to be clear. Thank you so much for all of you. Really helpful.

<TabItem x:Class="MyControls.Tab"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TabItem.Style>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabItem}">
                        <Grid>
                            <Border  Name="Border" Margin="0,0,-4,0" BorderThickness="1,1,1,1" CornerRadius="2,12,0,0" >
                                <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True"/>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>

                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Panel.ZIndex" Value="100" />
                                <Setter TargetName="Border" Property="Background" Value="Red" />
                                <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                            </Trigger>

                            <Trigger Property="IsSelected" Value="False">
                                <Setter TargetName="Border" Property="Background" Value="Green" />                                
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Border" Property="Background" Value="Orange" />
                            </Trigger>

                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TabItem.Style>
</TabItem>

------------- Edit 3 ----------------

I am not clear enough I think. Here is what it is now: It's working fine if the mouse is over other tabs: enter image description here

But When the mouse is over the circled area, the background color of the selected item should be red instead of orange: enter image description here

---------------Edit 4 -------------------

Thanks for all of you for your reply. Now after a long discussion with my users and some others, we would like to change the background color dynamically. The user wants to set the color by themselves. Basically, I need first do some binding (if this is the term). I've tried the following. However, the selected tab is not with red background. I used the Snoop to check out, it turns out that the Background is set as red locally. I am a little confusing here. I've asked around, and someone gave me the suggestion to use TemplateBinding since it is under ControlTemplate. But, I've tried to create dependency property and something like that. But just I don't understand why should I use TemplateBinding since I read some article said that it is for compile time binding. I am totally confused. I am new to WPF, I am sorry if the question is low level question. Thanks again!

<TabItem x:Class="MyControl.Tab"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TabItem.Style>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabItem}">
                        <Grid>
                            <Border  Name="Border" Margin="0,0,-4,0" BorderThickness="1,1,1,1" CornerRadius="2,12,0,0" >
                                <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True"/>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Panel.ZIndex" Value="100" />
                                <Setter TargetName="Border" Property="Background" Value="{Binding SelectedBgClr}" />
                                <Setter Property="Foreground" Value="Yellow" />
                                <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                            </Trigger>                            
                            <Trigger Property="IsSelected" Value="False">
                                <Setter TargetName="Border" Property="Background" Value="Green" /> 
                                <Setter Property="Foreground" Value="AliceBlue"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Border" Property="Background" Value="Orange" />
                                <Setter Property="Foreground" Value="Black"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TabItem.Style>
</TabItem>

The behind code is:

 public Tab()
        {
            SelectedBgClr = new SolidColorBrush(Colors.Red);
            //UnSelectedBgClr = new SolidColorBrush(Colors.Green);
            //HoverBgClr = new SolidColorBrush(Colors.Orange);
            InitializeComponent();

        }
public static readonly DependencyProperty SelectedBgClrProperty = DependencyProperty.Register("SelectedBgClr", typeof(Brush), typeof(Tab), new UIPropertyMetadata(null));
public Brush SelectedBgClr
{
    get
    {
        return (Brush)GetValue(SelectedBgClrProperty);
    }
    set
    {
        SetValue(SelectedBgClrProperty, value);
    }
}
Payson
  • 508
  • 4
  • 11
  • 22
  • 2
    Clearly all answers to your question are answered by XAML because **That's what XAML is for** – Federico Berasategui Dec 12 '13 at 22:26
  • 1
    HighCore is right -- selected/unselected/hover are set by the visual state definitions in the control template. See here: http://msdn.microsoft.com/en-us/library/ms754137(v=vs.110).aspx – McGarnagle Dec 12 '13 at 22:40
  • Thanks @HighCore!! I see. However, this is the design of my project. No XAML... (I personally don't like the design either).I am working on it. Hope to get something later. Anyway, XAML is supposed to be compiled to codes. – Payson Dec 13 '13 at 15:33
  • 1
    @Payson not really. XAML is NOT compiled to code. XAML is compiled to BAML and read as a stream. Again, use XAML for the UI and code for the business logic. Don't code the UI. WPF is not winforms. learn that. – Federico Berasategui Dec 13 '13 at 15:35
  • @McGarnagle Thanks! I have read the msdn document you provided. It's helpful. However, still need to do this in C# codes. Thanks a lot. I really appreciate it. – Payson Dec 13 '13 at 15:36
  • 1
    If other people keep pushing the project in the wrong way, the project is going to fail miserably and I am going to stare down on it when it's burning in the floor and laugh. And then I'll say, *"I told you WPF was not like crappy winforms"* – Federico Berasategui Dec 13 '13 at 15:37
  • @HighCore I can't agree with you more. Well, the fact is I am not the designer. – Payson Dec 13 '13 at 15:48
  • @Payson then go and say that to anybody who's in charge. You're setting yourself for a big disastrous failure. Your code will be 100% unmaintainable and difficult to scale. And your end product will suffer a huge quality degradation as a result. Whoever took the decision of using WPF like winforms clearly does not understand WPF at all. – Federico Berasategui Dec 13 '13 at 15:49
  • @HighCore trust me, I want to speak out more than anyone. But it's not that easy. Well Thank you so much. I am collecting all the comments against the current design and will speak out later. I really appreciate your comments. – Payson Dec 13 '13 at 16:40
  • @Payson I spend part of my time refactoring parts of a big Line-of-Business WPF application, which was initally developed by former employees with a "winforms mentality". My code is way cleaner, shorter, maintainable, and even much more reusable. After all it's just `simple, simple properties and INotifyPropertyChanged`. – Federico Berasategui Dec 13 '13 at 16:44
  • @HighCore oh really? Sounds exciting. Any documents or examples about that? – Payson Dec 13 '13 at 18:06
  • @Payson see [Rachel's Excellent Answer](http://stackoverflow.com/a/15684569/643085) and related blog posts. – Federico Berasategui Dec 13 '13 at 18:09
  • @HighCore Hi I am doing it with XAML now. YEAH!! Well I still have questions about that. Could you please see my Edit 2 and give any hints? – Payson Dec 13 '13 at 20:59
  • @Payson I just tested your XAML and it works fine. Can you post a screenshot of what you currently have and another one showing what you really need? – Federico Berasategui Dec 13 '13 at 21:03
  • @HighCore It's been updated. I attached two screenshots. I wish this could make myself clear enough. Thanks – Payson Dec 13 '13 at 21:54
  • @Payson Why are you creating `MyTab` instead of just using the default `TabItem`? You're setting yourself for a lot of trouble. Don't do that. See [`Alternatives to Writing a New Control`](http://msdn.microsoft.com/en-us/library/ms745025(v=vs.110).aspx#when_to_write_a_new_control) in MSDN. – Federico Berasategui Dec 13 '13 at 22:04
  • @HighCore I did this since I am working on some UI framework instead of UI directly. I've updated my question about the binding value. Could you please help me to check it when it is convenient to you? Thank you so much. – Payson Dec 16 '13 at 22:10

3 Answers3

5

The reason you're finding it difficult to get answers to your question is because you're going about it completely the wrong way; I can think of very few cases where changing controls in code as you're suggesting would be justified. WPF has been specifically designed to decouple visual state from code, ignore this at your own peril!

To actually answer your question though the following will do the trick...sort of...

foreach (TabItem item in tabControl.Items)
    item.Background = new SolidColorBrush(Colors.Blue);

If you do this then you'll notice it changes the background color of unselected tabs but not the currently selected tab. You'll also notice that highlighting a tab with the mouse will display another color yet again. There are in fact 7 different visual states for TabItem, adding code to cover each of these cases starts to get messy and certainly a lot more complex than using XAML.

The "proper" way to control background color via code is with XAML and data binding. First of all go to the Microsoft MSDN page for WPF TabControl Styles and Templates where you'll find the complete template for TabItem. Paste that into your Window.Resources section and now you can start playing around with the way it looks (don't forget to the add the namespaces and resources as well). If you play around with the various resources you'll notice that TabItems use a linear gradient, ControlLightColor is used for the top of all tabs, ControlMediumColor is used for the bottom of unselected tabs and ControlDarkColor is used for the bottom of the currently selected tab. To control this at run time you need to find all the instances of these resources scattered within the style and bind them to a property in your code, then write a ValueConverter that converts whatever your property is (I'm guessing a boolean) to a brush. Another (better) method, and one that doesn't require any extra code to be written at all, would be to use a DataTrigger that changes the visual appearence of the TabItem in response to your property changing value, but that's getting a little bit past the "beginner" stage and you'd need to provide more info on your specific case.

UPDATE: This seems to work for me:

void TabSet_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
    {
        foreach (TabItem item in tabControl.Items)
            item.Background = new SolidColorBrush(item.IsSelected ? Colors.Green : Colors.Red);
    }

I still say this is horribly wrong though. If you absolutely insist on doing this in code then you shouldn't be using WPF. It's completely the wrong technology, I cannot stress this strongly enough!

UPDATE #2: You're almost there, you just need to do this in the window that hosts the tab control:

<Window x:Class="MyWpfApplication.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300" WindowState="Maximized">

    <Window.Resources>

        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabItem}">
                        <Grid>
                            <Border  Name="Border" Margin="0,0,-4,0" BorderThickness="1,1,1,1" CornerRadius="2,12,0,0" >
                                <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True"/>
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>

                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="Panel.ZIndex" Value="100" />
                                <Setter TargetName="Border" Property="Background" Value="Red" />
                                <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                            </Trigger>

                            <Trigger Property="IsSelected" Value="False">
                                <Setter TargetName="Border" Property="Background" Value="Green" />
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Border" Property="Background" Value="Orange" />
                            </Trigger>

                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    </Window.Resources>

    <TabControl>
        <TabItem Header="Foo" />
        <TabItem Header="Bar" />
        <TabItem Header="Baz" />
    </TabControl>

</Window>
Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • Thank you so much @Mark Feldman. I am working on it now. What I have done now is as following: – Payson Dec 13 '13 at 15:39
  • I added an event SelectionChanged; and I set the background color there. Please see my edit since there is not enough space here. – Payson Dec 13 '13 at 15:41
  • Thanks for your updates. However, still, the background color doesn't change for the selected item. – Payson Dec 13 '13 at 20:32
  • @Payson have you put a breakpoint on the TabSet_SelectionChanged function to make sure that's getting called properly? – Mark Feldman Dec 13 '13 at 20:46
  • Thanks! I think you are right. I am doing it with WPF finally. Could you please see my new edit? – Payson Dec 13 '13 at 20:56
  • 1
    Updated again...not sure why you're trying to subclass TabItem though, it really isn't necessary in WPF. – Mark Feldman Dec 13 '13 at 21:06
  • Hi, I've updated the question. Could you please help me to check it out? Thanks – Payson Dec 16 '13 at 22:11
  • 1
    @MarkFeldman the link you provided to the MSDN resource is no longer working – Steffen Winkler Oct 16 '15 at 14:22
  • 1
    @SteffenWinkler thanks for the heads-up, I was lamenting to a colleague just the other day how annoyed I get at the MSDN links constantly going down. I've updated the link in my answer accordingly. – Mark Feldman Oct 17 '15 at 08:04
1

WPF lets you create a new custom control type based on an existing control, you can then fill it out with the template/style declaration on the Microsoft site and change the bits to suit you. Create a new user control called MyTabControl and replace the behind code with this:

public partial class MyTabControl : TabControl
{
    public static readonly DependencyProperty SelectedBgClrProperty = DependencyProperty.Register("SelectedBgClr",
        typeof(Brush), typeof(MyTabControl), new UIPropertyMetadata(null));

    [Category("Appearance")]
    public Brush SelectedBgClr
    {
        get
        {
            return (Brush)GetValue(SelectedBgClrProperty);
        }
        set
        {
            SetValue(SelectedBgClrProperty, value);
        }
    }

    public MyTabControl()
    {
        InitializeComponent();
    }
}

Now replace the xaml with this (you'll need to change the namespace to whatever your project is):

<TabControl x:Class="TabDemo.MyTabControl"
         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" 
         mc:Ignorable="d" 
         Name="tabControl"
         d:DesignHeight="300" d:DesignWidth="300">

<TabControl.Resources>

    <Style TargetType="{x:Type TabItem}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabItem}">
                    <Grid>
                        <Border  Name="Border" Margin="0,0,-4,0" BorderThickness="1,1,1,1" CornerRadius="2,12,0,0" >
                            <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True"/>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Panel.ZIndex" Value="100" />
                            <Setter TargetName="Border" Property="Background" Value="{Binding ElementName=tabControl, Path=SelectedBgClr}" />
                            <Setter Property="Foreground" Value="Yellow" />
                            <Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
                        </Trigger>
                        <Trigger Property="IsSelected" Value="False">
                            <Setter TargetName="Border" Property="Background" Value="Green" />
                            <Setter Property="Foreground" Value="AliceBlue"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="Orange" />
                            <Setter Property="Foreground" Value="Black"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</TabControl.Resources>

<TabControl.Style>
    <Style  TargetType="{x:Type TabControl}">
        <Setter Property="OverridesDefaultStyle"
          Value="True" />
        <Setter Property="SnapsToDevicePixels"
          Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabControl}">
                    <Grid KeyboardNavigation.TabNavigation="Local">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty="(Border.BorderBrush).
                    (SolidColorBrush.Color)">
                                            <EasingColorKeyFrame KeyTime="0"
                                         Value="#FFAAAAAA" />
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <TabPanel x:Name="HeaderPanel"
                    Grid.Row="0"
                    Panel.ZIndex="1"
                    Margin="0,0,4,-1"
                    IsItemsHost="True"
                    KeyboardNavigation.TabIndex="1" />
                        <Border x:Name="Border"
                  Grid.Row="1"
                  BorderThickness="1"
                  CornerRadius="2"
                  KeyboardNavigation.TabNavigation="Local"
                  KeyboardNavigation.DirectionalNavigation="Contained"
                  KeyboardNavigation.TabIndex="2" Background="{Binding ElementName=tabControl, Path=SelectedBgClr}">
                            <ContentPresenter x:Name="PART_SelectedContentHost"
                              Margin="4"
                              ContentSource="SelectedContent" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</TabControl.Style>

Now in your MainWindow or whatever just use it as you would a regular TabControl, the SelectedBgClr will set both the selected tab header and the main panel background (if you look in the XAML above you've see bindings for both):

<local:MyTabControl SelectedBgClr="Red">
        <TabItem Header="Foo"  />
        <TabItem Header="Bar" />
        <TabItem Header="Baz" />
    </local:MyTabControl>

Notice that the behind-code is minimal, it's still XAML that's doing the bulk of the work and MyTabControl is simply used as a wrapper for the dependency property. In a real application you would use something called an attached property so that you wouldn't need to derive a whole new TabControl class.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • Thanks! Mark! Well, I've changed it as you suggested. However, my case is a little bit different here. I am not going to set the SelectedBgClr="Red" in any XAML file. Instead, the user will set them with the C# codes (or other ways). All I need is to open a DependencyProperty for my user to take care of the background color of the tab. – Payson Dec 17 '13 at 19:32
  • That's fine, you can set dependency properties in code by doing something like "myTab.SetValue(MyTabControl.SelectedBgClrProperty, Brushes.Red)". It's also possible to declare a brush in your block and change its properties at runtime, in which case you refer to it as a DynamicResource instead of a StaticResource. – Mark Feldman Dec 17 '13 at 19:52
0

Put this in your TabControlChanged event:

foreach (TabItem AllTabItems in MyTabControl.Items) // Change MyTabControl with your TabControl name
{
    if (!AllTabItems.IsSelected)
    {
        // do something for all unselected tabitems
    }
    else if (AllTabItems.IsSelected)
    {
       // do something for the selected tabitem
    }
    else if (AllTabItems.IsMouseOver)
    {
       // do something for mouseover tabitem
    }
}
Marco Concas
  • 1,665
  • 20
  • 25