0

I am using Material Design in XAML and I'm trying to create a navigation drawer. I want the drawer to slide in and out on opening/closing button click. For this purpose I've created two buttons for opening and closing, keeping one of them invisible. On top of that I have animations declared in window's resources which are used in EventTriggers section. But apart from starting animations, I also have to hide the clicked button and show the another one.

For now I've created an event handler for both buttons and I'm keeping the code for managing visibility in code-behind as it seemed to be the easiest solution. However I'm afraid it is breaking the MVVM pattern, but on the other hand, I shouldn't bind this event to a command in a view model because the code is messing with UI-related things.

Animations and Window Triggers:

<Window.Resources>
    <Storyboard x:Key="OpenMenu">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Storyboard.TargetName="Menu">
            <EasingDoubleKeyFrame Value="70" KeyTime="0:0:0"/>
            <EasingDoubleKeyFrame Value="200" KeyTime="0:0:0.5"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>

    <Storyboard x:Key="CloseMenu">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Storyboard.TargetName="Menu">
            <EasingDoubleKeyFrame Value="200" KeyTime="0:0:0"/>
            <EasingDoubleKeyFrame Value="70" KeyTime="0:0:0.5"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>

<Window.Triggers>
    <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="OpenMenuButton">
        <BeginStoryboard Storyboard="{StaticResource OpenMenu}"/>
    </EventTrigger>
    <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="CloseMenuButton">
        <BeginStoryboard Storyboard="{StaticResource CloseMenu}"/>
    </EventTrigger>
</Window.Triggers>

The navigation part with buttons:

<Grid Grid.Row="1" HorizontalAlignment="Left" Width="70" Background="CornflowerBlue" x:Name="Menu">
<StackPanel>
    <Grid>
        <Button Style="{DynamicResource MaterialDesignFloatingActionButton}" 
                Background="{x:Null}" BorderBrush="{x:Null}" x:Name="OpenMenuButton" Click="ToggleMenu"
                HorizontalAlignment="Right" Width="70">

            <md:PackIcon Kind="Menu" Width="35" Height="35"/>
        </Button>

        <Button Style="{DynamicResource MaterialDesignFloatingActionButton}" 
                Background="{x:Null}" BorderBrush="{x:Null}" Visibility="Collapsed"
                x:Name="CloseMenuButton" Click="ToggleMenu" HorizontalAlignment="Right">

            <md:PackIcon Kind="ArrowLeft" Width="35" Height="35"/>
        </Button>
    </Grid>
</StackPanel>
</Grid>   

And code-behind for managing visibility:

private void ToggleMenu(object sender, RoutedEventArgs e)
{
    ToggleButtonVisibility(OpenMenuButton);
    ToggleButtonVisibility(CloseMenuButton);
}

private void ToggleButtonVisibility(Button b)
{
    if (b.Visibility == Visibility.Collapsed || b.Visibility == Visibility.Hidden)
        b.Visibility = Visibility.Visible;
    else
        b.Visibility = Visibility.Collapsed;
}

Is there a way to implement this in respect to MVVM pattern (no code-behind) and to keep click actions in one place (because right now they are split in 2 parts: animation + visibility toggle)?

Chris
  • 41
  • 2
  • 6
  • 1
    how about using a `ToggleButton` with a custom template – Efraim Newman Aug 05 '19 at 17:27
  • I wasn't even aware that something like ToggleButton exists in WPF. Turns out that Material Design has number of styles for it. Now it's a really easy task. Thank you – Chris Aug 05 '19 at 19:01
  • I don't think this violates MVVM in any way, as the code-behind is doing purely visual things and nothing business related. MVVM don't means "no code-behind at all", just keep the logic out of it. – Alejandro Aug 05 '19 at 19:15

1 Answers1

0

Add a bool flag to the viewmodel (e.g. IsAnimationStarted) And then just bind it to buttons visibility with converter

<UserControl.Resources>
..
        <local:InvertableBooleanToVisibilityConverter x:Key="bool2visible" />
..
</UserControl.Resources>
..
<Grid>
        <Button Style="{DynamicResource MaterialDesignFloatingActionButton}" 
                Background="{x:Null}" BorderBrush="{x:Null}" x:Name="OpenMenuButton"  
                Visibility="{Binding IsAnimationStarted, Converter={StaticResource bool2visible}, ConverterParameter=Inverted}"
                HorizontalAlignment="Right" Width="70">

            <md:PackIcon Kind="Menu" Width="35" Height="35"/>
        </Button>

        <Button Style="{DynamicResource MaterialDesignFloatingActionButton}" 
                Background="{x:Null}" BorderBrush="{x:Null}" Visibility="Collapsed"
                x:Name="CloseMenuButton"
Visibility="{Binding IsAnimationStarted, Converter={StaticResource bool2visible}, ConverterParameter=Normal}" 
 HorizontalAlignment="Right">

            <md:PackIcon Kind="ArrowLeft" Width="35" Height="35"/>
        </Button>
    </Grid>

Visibility converter: https://stackoverflow.com/a/2427307/6468720

  • 1
    Prefer to use the build-in [`BooleanToVisibilityConverter`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.booleantovisibilityconverter?view=netframework-4.8) – BionicCode Aug 05 '19 at 22:18