locked
Visual states & command not working for custom button in Windows 8/8.1 app

    Question

  • I am creating a custom button for a Windows 8.1 universal app. I want to bind MVVM command with it. My all code is given below. Command binding works with default button but not working with custom button. Moreover, visual states are also not working in custom button. I tried my code in VS 2012/2013 both. What's wrong with my code?

    MyButton.xaml (There's nothing in code behind except constructor)

    <ButtonBase
        x:Class="App5.MyButton"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App5"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300"
        d:DesignWidth="400">
    
        <ButtonBase.Resources>
            <Style TargetType="local:MyButton">
                <Setter Property="Background" Value="{ThemeResource ButtonBackgroundThemeBrush}"/>
                <Setter Property="Foreground" Value="{ThemeResource ButtonForegroundThemeBrush}"/>
                <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}"/>
                <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}"/>
                <Setter Property="Padding" Value="12,4,12,4"/>
                <Setter Property="HorizontalAlignment" Value="Left"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
                <Setter Property="FontWeight" Value="SemiBold"/>
                <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="local:MyButton">
                            <Grid>
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="Normal"/>
                                        <VisualState x:Name="PointerOver">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Border">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPointerOverBackgroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPointerOverForegroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Pressed">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Border">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedBackgroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedForegroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Disabled">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Border">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBackgroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Border">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBorderThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledForegroundThemeBrush}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                    <VisualStateGroup x:Name="FocusStates">
                                        <VisualState x:Name="Focused">
                                            <Storyboard>
                                                <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualWhite"/>
                                                <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualBlack"/>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="Unfocused"/>
                                        <VisualState x:Name="PointerFocused"/>
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>
                                <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="3">
                                    <ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                </Border>
                                <Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="1.5" StrokeEndLineCap="Square" Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}" StrokeDashArray="1,1"/>
                                <Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="0.5" StrokeEndLineCap="Square" Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}" StrokeDashArray="1,1"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ButtonBase.Resources>
    
        <Grid>
    
        </Grid>
    </ButtonBase>


    MainPage.xaml

    <Page x:Name="page"
        ......>
    
    
        <Grid x:Name="root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <local:MyButton x:Name="btn" Content="Custom"  Command="{Binding ViewModel.DoWorkCommand, ElementName=page}" />
            <Button HorizontalAlignment="Right" Content="Default"  Command="{Binding ViewModel.DoWorkCommand, ElementName=page}" />
        </Grid>

    MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            viewModel = new ViewModel();
            //btn.Command = viewModel.DoWorkCommand; That's working but I don't want that.
         }
    
        private ViewModel viewModel;
        public ViewModel ViewModel
        {
            get { return this.viewModel; }
        }
    }

    ViewModel.cs

    public class ViewModel
    {
        RelayCommand _doWorkCommand;
    
        public RelayCommand DoWorkCommand
        {
            get
            {
                if (_doWorkCommand == null)
                {
                    _doWorkCommand = new RelayCommand(DoWork);
                }
                return _doWorkCommand;
            }
            set
            {
                _doWorkCommand = value;
            }
        }
    
        public void DoWork()
        {
        }
    }

    RelayCommand.cs

    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;
    
        public event EventHandler CanExecuteChanged;
    
        public RelayCommand(Action execute)
            : this(execute, null)
        {
        }
    
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }
    
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }
    
        public void Execute(object parameter)
        {
            _execute();
        }
    
        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }


    Wednesday, May 07, 2014 7:08 PM

Answers

  • I created a custom control (using Templated Control template in Visual Studio - reference here ) and inherited the Control from Button instead of Control.

    "Themes" folder is created in your project which contains the Style for the control in Generic.xaml - where the Visual State behavior and UI template is defined.

    Looks something like this :

     public sealed class CustomControl1 : Button
        {
            public CustomControl1()
            {
                this.DefaultStyleKey = typeof(CustomControl1);
            }
        }


    I added some code for testing and it seems to be working for me :

    <Style TargetType="local:CustomControl1"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:CustomControl1"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="Blue" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames .....

    ......


    Also tested the Command binding code for the custom Button control in XAML and that works too.

     <local:CustomControl1 Command="{Binding ViewModel.DoWorkCommand, ElementName=page}" Content="Hello"  Height="30" Width="100"></local:CustomControl1>

    I believe you can try the Templated Control route to solve your problem.

    Sagar

    Thursday, May 08, 2014 11:50 AM

All replies

  • I created a custom control (using Templated Control template in Visual Studio - reference here ) and inherited the Control from Button instead of Control.

    "Themes" folder is created in your project which contains the Style for the control in Generic.xaml - where the Visual State behavior and UI template is defined.

    Looks something like this :

     public sealed class CustomControl1 : Button
        {
            public CustomControl1()
            {
                this.DefaultStyleKey = typeof(CustomControl1);
            }
        }


    I added some code for testing and it seems to be working for me :

    <Style TargetType="local:CustomControl1"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:CustomControl1"> <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="Blue" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames .....

    ......


    Also tested the Command binding code for the custom Button control in XAML and that works too.

     <local:CustomControl1 Command="{Binding ViewModel.DoWorkCommand, ElementName=page}" Content="Hello"  Height="30" Width="100"></local:CustomControl1>

    I believe you can try the Templated Control route to solve your problem.

    Sagar

    Thursday, May 08, 2014 11:50 AM
  • Thanks Sagar for your answer. Your code works for me. I tried to use tilt effect of Callisto toolkit in my button's style (Actually in border of button's template). At that time the command is not working, what would be solution for that?
    Friday, May 09, 2014 10:02 AM
  • Hello,

    I am not familiar with Callisto - I think the best place to ask about should be approach/consult the product's discussion forums .

    Sagar

    Friday, May 09, 2014 1:06 PM