baozi
baozi

Reputation: 709

How to use custom styles to bind with custom command?

Here's my situation. So far i know i can create resource dictionary to define custom styles such as tab control. And i'd like to use there styles in the main window. The problem is where to define events such as button click for buttons in styles? To say I want to do Visibility="Binding {Button A's click}"?

<Window x:Class="test_tabControl3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<DockPanel LastChildFill="True">
    <Grid DockPanel.Dock="Bottom">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="Zoom:"
                VerticalAlignment="Center"
                Margin="5"/>
        <!-- Allows to zoom the TabControl (see the ScaleTransform defined on the TC) -->
        <Slider x:Name="uiScaleSlider" 
             Grid.Column="1"
             ToolTip="Drag the slider to change the zoom-level ..."
             SmallChange="0.1"
             LargeChange="1"
             Minimum="1" 
             Maximum="10"
             Value="2" 
             Margin="5"/>
    </Grid>
    <Grid x:Name="Layer1" Visibility="Binding {Button A's click}">
    <TabControl x:Name="tc" Margin="5" SelectedIndex="0" Style="{StaticResource FirstTab}" Visibility="Visible">
        <TabControl.LayoutTransform>
            <!-- Allows to zoom the control's content using the slider -->
            <ScaleTransform CenterX="0" 
                     CenterY="0"
                     ScaleX="{Binding ElementName=uiScaleSlider,Path=Value}"
                     ScaleY="{Binding ElementName=uiScaleSlider,Path=Value}"/>
        </TabControl.LayoutTransform>
        <TabItem Header="Tab 1" Style="{StaticResource FirstTabItem}">
            <Canvas Background="AliceBlue"/>
        </TabItem>
        <TabItem Header="Tab 2" Style="{StaticResource FirstTabItem}">
            <Canvas Background="Lavender"/>
        </TabItem>
        <TabItem Header="Tab 3" IsEnabled="False"  Style="{StaticResource FirstTabItem}"
              ToolTip="I'm disabled.">
            <Canvas Background="PaleGreen"/>
        </TabItem>
    </TabControl>
    </Grid>
</DockPanel>
</Window>

enter image description here

Update The TabControl is defined in a resource dictionary

<Style TargetType="{x:Type TabControl}" x:Key="FirstTab">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TabControl">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                            <ColumnDefinition Width="Auto"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <Border Background="AliceBlue"
                             Padding="{StaticResource TabItemPanel_Padding}">
                            <!-- This is the area in which TabItems (the strips) will be drawn. -->
                            <TabPanel IsItemsHost="True"/>
                        </Border>
                        <Button Grid.Column="1">A</Button>
                    </Grid>
                    <Border BorderThickness="1,0,1,1"
....

Update2 Some more questions about Style Templates. I know I need to define ContentPresenter ContentSource to render the property in templates. Below I have two styles first for TabControl and second for Tabitem. In the TabItem style,i have ContentSource="Header"under which Header is a property of tabItem, while In the TabControl style ContentSource="SelectedContent". So i wanna know why it can not be ContentSource="Content" under which Content is a property of tabItem defined in tabItem control.

<Style TargetType="{x:Type TabControl}">
    <Setter Property="Template">
    ....
    <ContentPresenter ContentSource="SelectedContent" Margin="0"/>
</Setter>
</Style>

<Style TargetType="{x:Type TabItem}" >
                <Setter Property="Header"
                    Value="{Binding Header}" />
                <Setter Property="Content"
                    Value="{Binding Content}" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="TabItem">
                            <Grid Height="35" VerticalAlignment="Bottom">
                                <Border Name="Border"
                         Background="{StaticResource TabItem_BackgroundBrush_Unselected}"
                         BorderBrush="{StaticResource TabItem_BorderBrush_Selected}" 
                         Margin="{StaticResource TabItemMargin_Base}"
                         BorderThickness="2,1,1,0" 
                         CornerRadius="3,3,0,0" 
                         >
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <!-- Text / TabItem's Caption -->
                                            <ColumnDefinition/>
                                            <!-- Close button -->
                                            <ColumnDefinition/>
                                        </Grid.ColumnDefinitions>
                                        <ContentPresenter x:Name="ContentSite"
                                         VerticalAlignment="Center"
                                         HorizontalAlignment="Center"
                                         ContentSource="Header"
                                         Margin="7,2,12,2"
                                         RecognizesAccessKey="True">

                                        </ContentPresenter>

Upvotes: 0

Views: 943

Answers (1)

pushpraj
pushpraj

Reputation: 13669

here is a complete example for how to use commands, also this example demonstrates how to use view model to populate the tab controls too.

this will give you a start on building mvvm based applications

xaml

<Grid xmlns:l="clr-namespace:CSharpWPF">
    <Grid.DataContext>
        <l:ViewModel />
    </Grid.DataContext>
    <TabControl ItemsSource="{Binding TabItems}"
                SelectedItem="{Binding CurrentItem}"
                x:Name="tabControl">
        <TabControl.Resources>
            <Style TargetType="TabItem">
                <Setter Property="Header"
                        Value="{Binding Header}" />
                <Setter Property="Content"
                        Value="{Binding Content}" />
            </Style>
        </TabControl.Resources>
    </TabControl>
    <Button Content="X"
            IsEnabled="{Binding HasItems,ElementName=tabControl}"
            Command="{Binding CloseTab}"
            CommandParameter="{Binding SelectedItem,ElementName=tabControl}"
            HorizontalAlignment="Right"
            VerticalAlignment="Top" />
</Grid>

I keep the example small, you may however apply any style you want, all you need is to locate the appropriate binding path.

code

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;

namespace CSharpWPF
{
    class ViewModel : INotifyPropertyChanged
    {
        public ViewModel()
        {
            TabItems = new ObservableCollection<TabData>();

            //dummy data
            TabItems.Add(new TabData() { Header = "Tab 1", Content = "Tab content 1" });
            TabItems.Add(new TabData() { Header = "Tab 2", Content = "Tab content 2" });
            TabItems.Add(new TabData() { Header = "Tab 3", Content = "Tab content 3" });
            TabItems.Add(new TabData() { Header = "Tab 4", Content = "Tab content 4" });
            TabItems.Add(new TabData() { Header = "Tab 5", Content = "Tab content 5" });

            CurrentItem = TabItems[3];

            CloseTab = new SimpleCommand(OnCloseTab);
        }

        public ObservableCollection<TabData> TabItems { get; private set; }

        private TabData _currentItem;

        public TabData CurrentItem
        {
            get { return _currentItem; }
            set { _currentItem = value; }
        }

        public ICommand CloseTab { get; private set; }

        private void OnCloseTab(object obj)
        {
            TabData tab = obj as TabData;
            if (tab != null && TabItems.Contains(tab))
            {
                TabItems.Remove(tab);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    class TabData
    {
        public string Header { get; set; }
        public object Content { get; set; }
    }

    class SimpleCommand : ICommand
    {
        Action<object> action;
        public SimpleCommand(Action<object> action)
        {
            this.action = action;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            action(parameter);
        }
    }
}

give this a try in a new test project and share your feedback


EDIT

after looking at the sample you've sent I must say that you've done a nice job. you are almost there, you just missed a trivial thing

the command on the button is working in the example code because the button's DataContext is same where the command is located, but when you place the button on the tab itself the data context is changed to the TabData which does not have that command

so you need to find the appropriate datacontext which is on the tabitem's parent tab control, you can use RelativeSource with FindAncestor to find the appropriate control and bind to the DataContext's CloseTab property which is the desired command

example

Command="{Binding DataContext.CloseTab,RelativeSource={RelativeSource FindAncestor,AncestorType=TabControl}}"

also to figure out this kind of issue keep an eye on the Output window, I found this when I debug your code

System.Windows.Data Error: 40 : BindingExpression path error: 'CloseTab' property not found on 'object' ''TabData' (HashCode=37561097)'. BindingExpression:Path=CloseTab; DataItem='TabData' (HashCode=37561097); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

this tell me that the binding is attempting to resolve the CloseTab property on an instance of TabData class


for you second question you can simply refer to WPF: Multiple content presenters in a custom control? it has one of the clear answer on the same

Upvotes: 1

Related Questions