2

I'm trying to implement a control, which works similarly to a button in Windows Phone's keyboard: when pressed and held, it should display additional panel with additional option.

I managed to display the additional part after holding the button, but now I'm trying to style borders to react to user's pointer movement. I tried the approach I used before - with VisualStateManager - but for some reason it simply does not work. Can you tell me, why?

Xaml:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ExpandKeyTester">

    <Style TargetType="local:ExpandKey">
        <Setter Property="BorderBrush" Value="{ThemeResource PhoneForegroundBrush}" />
        <Setter Property="BorderThickness" Value="{ThemeResource PhoneBorderThickness}" />
        <Setter Property="Background" Value="{ThemeResource PhoneBackgroundBrush}" />
        <Setter Property="OverlayBrush" Value="{ThemeResource PhoneAccentBrush}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ExpandKey">
                    <Grid x:Name="PART_MainGrid">
                        <Border x:Name="PART_MainBorder" 
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="Pressed">

                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="PART_MainBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Red" />
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="PART_MainBorder">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Blue" />
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <TextBlock>Test</TextBlock>
                        </Border>

                        <Grid x:Name="PART_OverlayGrid" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                              Background="{TemplateBinding OverlayBrush}" Visibility="Collapsed">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="1*" />
                                <RowDefinition Height="1*" />
                            </Grid.RowDefinitions>

                            <Border x:Name="PART_AltBtn" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}" />
                            <Border x:Name="PART_Btn" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}" />
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Code:

public sealed class ExpandKey : Control
{
    private const int OVERLAY_MARGIN = 10;

    private Grid mainGrid;
    private Border mainBorder;
    private Grid overlayGrid;
    private Border altBtn;
    private Border btn;

    private void AttachEvents()
    {
        if (mainBorder != null)
            mainBorder.Holding += HandleMainBorderHolding;
        if (overlayGrid != null)
            overlayGrid.PointerReleased += HandleOverlayGridPointerReleased;
    }

    private void DetachEvents()
    {
        if (mainBorder != null)
            mainBorder.Holding -= HandleMainBorderHolding;
        if (overlayGrid != null)
            overlayGrid.PointerReleased -= HandleOverlayGridPointerReleased;
    }

    private void MeasureOverlayGrid()
    {
        if (mainGrid == null ||
            mainBorder == null ||
            overlayGrid == null ||
            altBtn == null ||
            btn == null)
            throw new InvalidOperationException("Internal error: missing template parts!");

        var newTopMargin = -(mainGrid.ActualHeight + OVERLAY_MARGIN);
        var newBottomMargin = -OVERLAY_MARGIN;
        var newLeftMargin = -OVERLAY_MARGIN;
        var newRightMargin = -OVERLAY_MARGIN;

        overlayGrid.Margin = new Thickness(newLeftMargin, newTopMargin, newRightMargin, newBottomMargin);

        btn.Margin = new Thickness(OVERLAY_MARGIN, 0, OVERLAY_MARGIN, OVERLAY_MARGIN);
        altBtn.Margin = new Thickness(OVERLAY_MARGIN, OVERLAY_MARGIN, OVERLAY_MARGIN, OVERLAY_MARGIN);
    }

    private void HandleMainBorderHolding(object sender, HoldingRoutedEventArgs e)
    {
        MeasureOverlayGrid();
        overlayGrid.Visibility = Visibility.Visible;
        e.Handled = false;
    }

    private void HandleOverlayGridPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        overlayGrid.Visibility = Visibility.Collapsed;
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        DetachEvents();

        mainGrid = GetTemplateChild("PART_MainGrid") as Grid;
        mainBorder = GetTemplateChild("PART_MainBorder") as Border;
        overlayGrid = GetTemplateChild("PART_OverlayGrid") as Grid;
        altBtn = GetTemplateChild("PART_AltBtn") as Border;
        btn = GetTemplateChild("PART_Btn") as Border;

        AttachEvents();
    }

    public ExpandKey()
    {
        this.DefaultStyleKey = typeof(ExpandKey);
    }

    #region OverlayBrush dependency property

    public Brush OverlayBrush
    {
        get { return (Brush)GetValue(OverlayBrushProperty); }
        set { SetValue(OverlayBrushProperty, value); }
    }

    // Using a DependencyProperty as the backing store for OverlayBrush.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty OverlayBrushProperty =
        DependencyProperty.Register("OverlayBrush", typeof(Brush), typeof(ExpandKey), new PropertyMetadata(null));

    #endregion
}
Spook
  • 25,318
  • 18
  • 90
  • 167
  • To what `UserControl` are you referring in your question title? Did you mean "custom control"? A `UserControl` is a bit different from standard templated controls. – Mike Strobel Oct 23 '14 at 18:41
  • @MikeStrobel I struggle for some time to finally remember, what difference is between "user control" and "custom control". Obviously in this case I'm working with a templated control, so to call it (sounds similarly to what is in the "Add | New..." dialog window). – Spook Oct 23 '14 at 18:43
  • A `UserControl` is basically a windowless 'form' or 'page'. Its visual tree is defined directly by its content, whereas a custom control typically just inherits from `Control` and has its appearance governed by a template. Use custom controls for generic, reusable widgets and components. Use user controls for views. – Mike Strobel Oct 23 '14 at 18:45
  • 1
    Any reason you're using `VisualStateManager` instead of `Triggers`? I think the problem is that your control never switches its visual state to `Pressed`; by default, `Control` only supports the `Valid`, `ValidFocused`, and `ValidUnfocused` states. `VisualStateManager` is, in my opinion, a rather hacky solution inherited from Silverlight. I avoid it whenever possible and stick to the traditional WPF pattern of using Triggers and Setters. – Mike Strobel Oct 23 '14 at 18:54
  • @MikeStrobel AFAIK there are no triggers in Windows Phone. Can you provide a working example? – Spook Oct 23 '14 at 22:56

1 Answers1

0

I think your TargetProperty is incorrect,

I would use

TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" AND TargetProperty="(Border.Background).(SolidColorBrush.Color)"

<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" Storyboard.TargetName="PART_MainBorder">
    <DiscreteObjectKeyFrame KeyTime="0" Value="Red" />
</ObjectAnimationUsingKeyFrames>

<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="PART_MainBorder">
    <DiscreteObjectKeyFrame KeyTime="0" Value="Blue" />
</ObjectAnimationUsingKeyFrames>

See Color Change On List View Item where I basically do the same thing with a Storyboard to a ListView SelectedItem for reference.

Community
  • 1
  • 1
Chubosaurus Software
  • 8,133
  • 2
  • 20
  • 26