Search code examples
wpfmvvmrouted-events

Handle an event in code behind as well as Invoke a view model's command on some event


Here's the overview :

I am using a Custom control (CusCtrl) to show taskbar icon , it also has a Popup property. so when you click on the icon the CusCtrl shows the Popup.

I am setting the child of the pop up with a UserControl (lets say UC1).

I am setting the DataContext of CusCtrl with a ViewModel thus even the UC1 get binded with a respective ViewModel (lets say VM1)

Now the UC1 has some element - a Label, on clicking the label I need 2 things to happen:

  1. Invoke a command on the view model VM1 -

    From the command I need to pass some of the view model's properties as parameters and open some window UI.

  2. Close the PopUp -

    For this I have thought of listening the MouseUp Event in code behind of UserControl & then fire a routed event (FirePopUpClose - this event is defined in the UserControl UC1) which will be handled by the app & then from within the handler, Custom Conntrol's ClosePopUp method will be called.

I do know how to invoke command on the MouseUp event on label using the Interactivity dll, but then how can I raise the FirePopUpClose routed Event?

Or how do apply a MouseUp event handler on label as well as bind a command to that label ?

Am I even thinking this the right way or there's some better cleaner way to do some UI action as well as close the PopUp by sticking to MVVM?


Solution

  • what about the next solution; try to use Popup.IsOpen property (here is the information about, and here is the example of usage #867 – Controlling Whether a Popup Is Open Using Data Binding ). Bind it to UC1 DataContext directly or via DependencyProperty of CusCtrl user control (you have to create that property in your control that encapsulate the Popup). That's all, this way you will be able to manage popup to be opened or closed without the event.

    Update Try the next: 1. Main Xaml:

    <Window x:Class="PopupIsOpenDataBindingHelpAttempt.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:popupIsOpenDataBindingHelpAttempt="clr-namespace:PopupIsOpenDataBindingHelpAttempt"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <popupIsOpenDataBindingHelpAttempt:DemoMainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate x:Key="PopupInnerControlDataTemplateKey" DataType="{x:Type popupIsOpenDataBindingHelpAttempt:TaskBarDemoViewModel}">
            <Grid Width="150" Height="85">
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0" Text="{Binding TextString}"
                           Margin="5" Foreground="Red"
                           Width="150" TextWrapping="WrapWithOverflow"
                           VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
                <Button    Grid.Row="1" Content="Press to close" Command="{Binding PopupInnerButtonCommand}"
                        VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
            </Grid>
        </DataTemplate>
        <Image x:Key="ImageControl" Source="Pic/2015_10_16_Bing_en-US.jpg" IsHitTestVisible="False"/>
    </Window.Resources>
    <Grid Width="75" Height="75" HorizontalAlignment="Center" VerticalAlignment="Center">
        <popupIsOpenDataBindingHelpAttempt:TaskBarIconProjectDemo 
            ButtonContentProperty="{StaticResource ImageControl}"
            ButtonCommandProperty="{Binding ShowPopupCommand}"
            PopupIsOpenProperty="{Binding IsPopupOpen, UpdateSourceTrigger=PropertyChanged}"
            PopupInnerContentControlDataContext="{Binding TaskBarDemoViewModel, UpdateSourceTrigger=PropertyChanged}"
            PopupInnerContentControlContentTemplate="{StaticResource PopupInnerControlDataTemplateKey}"/>
    </Grid>
    

    2. Popup encapsulating XAMl:

    <UserControl x:Class="PopupIsOpenDataBindingHelpAttempt.TaskBarIconProjectDemo"
             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" x:Name="This">
    <Grid>
        <Button Style="{StaticResource SpecialButtonStyle}" Command="{Binding ElementName=This, Path=ButtonCommandProperty}" 
                Content="{Binding ElementName=This, Path=ButtonContentProperty}"></Button>
        <Popup IsOpen="{Binding ElementName=This, Path=PopupIsOpenProperty}">
            <ContentControl Content="{Binding ElementName=This, Path=PopupInnerContentControlDataContext}" 
                            ContentTemplate="{Binding ElementName=This, Path=PopupInnerContentControlContentTemplate}"/>
        </Popup>
    </Grid>
    

    3. Popup encapsulating control code behind (dependency properties):

    public partial class TaskBarIconProjectDemo : UserControl
    {
        public static readonly DependencyProperty ButtonCommandPropertyProperty = DependencyProperty.Register("ButtonCommandProperty", typeof (ICommand), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(ICommand)));
        public static readonly DependencyProperty PopupIsOpenPropertyProperty = DependencyProperty.Register("PopupIsOpenProperty", typeof (bool), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(bool)));
        public static readonly DependencyProperty PopupInnerContentControlDataContextProperty = DependencyProperty.Register("PopupInnerContentControlDataContext", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
        public static readonly DependencyProperty PopupInnerContentControlContentTemplateProperty = DependencyProperty.Register("PopupInnerContentControlContentTemplate", typeof (DataTemplate), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(DataTemplate)));
        public static readonly DependencyProperty ButtonContentPropertyProperty = DependencyProperty.Register("ButtonContentProperty", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
    
        public TaskBarIconProjectDemo()
        {
            InitializeComponent();
        }
    
        public ICommand ButtonCommandProperty
        {
            get { return (ICommand) GetValue(ButtonCommandPropertyProperty); }
            set { SetValue(ButtonCommandPropertyProperty, value); }
        }
    
        public bool PopupIsOpenProperty
        {
            get { return (bool) GetValue(PopupIsOpenPropertyProperty); }
            set { SetValue(PopupIsOpenPropertyProperty, value); }
        }
    
        public object PopupInnerContentControlDataContext
        {
            get { return (object) GetValue(PopupInnerContentControlDataContextProperty); }
            set { SetValue(PopupInnerContentControlDataContextProperty, value); }
        }
    
        public DataTemplate PopupInnerContentControlContentTemplate
        {
            get { return (DataTemplate) GetValue(PopupInnerContentControlContentTemplateProperty); }
            set { SetValue(PopupInnerContentControlContentTemplateProperty, value); }
        }
    
        public object ButtonContentProperty
        {
            get { return (object) GetValue(ButtonContentPropertyProperty); }
            set { SetValue(ButtonContentPropertyProperty, value); }
        }
    }
    

    4. View Models:

        public class DemoMainViewModel:BaseObservableObject
    {
        private bool _isOpen;
        private TaskBarDemoViewModel _taskBarDemoViewModel;
        private ICommand _showPopupCommnad;
    
        public DemoMainViewModel()
        {
            TaskBarDemoViewModel = new TaskBarDemoViewModel(ClosePopup, "Here you can put your content. Go for it...");
        }
    
        private void ClosePopup()
        {
            IsPopupOpen = false;
        }
    
        public bool IsPopupOpen
        {
            get { return _isOpen; }
            set
            {
                _isOpen = value;
                OnPropertyChanged();
            }
        }
    
        public TaskBarDemoViewModel TaskBarDemoViewModel    
        {
            get { return _taskBarDemoViewModel; }
            set
            {
                _taskBarDemoViewModel = value;
                OnPropertyChanged();
            }
        }
    
        public ICommand ShowPopupCommand
        {
            get { return _showPopupCommnad ?? (_showPopupCommnad = new RelayCommand(ShowPopup)); }
        }
    
        private void ShowPopup()
        {
            IsPopupOpen = true;
        }
    }
    
    public class TaskBarDemoViewModel:BaseObservableObject
    {
        private readonly Action _closePopupCommand;
        private ICommand _command;
        private string _textString;
    
        public TaskBarDemoViewModel(Action closePopupCommand, string content)
        {
            _closePopupCommand = closePopupCommand;
            TextString = content;
        }
    
        public ICommand PopupInnerButtonCommand
        {
            get { return _command ?? (_command = new RelayCommand(TargetMethod)); }
        }
    
        private void TargetMethod()
        {
            //add your logic here
            if(_closePopupCommand == null) return;
            _closePopupCommand();
        }
    
        public string TextString
        {
            get { return _textString; }
            set
            {
                _textString = value;
                OnPropertyChanged();
            }
        }
    }
    

    5. Button style (change this as your need):

        <Color x:Key="ButtonLowerPartKey">#FFD5E0EE</Color>
        <Color x:Key="ButtonUpperPartKey">#FFEAF1F8</Color>
        <Color x:Key="PressedColorButtonLowerPartKey">#FFF4C661</Color>
        <Color x:Key="PressedButtonUpperPartKey">#FFF4CC87</Color>
        <Color x:Key="HooveredButtonLowerPartKey">#FFFFD06D</Color>
        <Color x:Key="HooveredButtonUpperPartKey">#FFFFF0DF</Color>
        <Style x:Key="SpecialButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Padding" Value="5">
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid x:Name="Grid">
                            <Ellipse x:Name="ButtonControlBorder" Stroke="{TemplateBinding BorderBrush}" 
                                     StrokeThickness="{TemplateBinding BorderThickness}" 
                                     Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                                <Ellipse.Fill>
                                    <LinearGradientBrush x:Name="BrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
                                        <LinearGradientBrush.GradientStops>
                                            <GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
                                            <GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
                                            <GradientStop Offset="0.5" Color="{StaticResource ButtonLowerPartKey}" />
                                        </LinearGradientBrush.GradientStops>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <Ellipse x:Name="Pressed" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Opacity="0">
                                <Ellipse.Fill>
                                    <LinearGradientBrush x:Name="PressedBrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
                                        <LinearGradientBrush.GradientStops>
                                            <GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
                                            <GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
                                            <GradientStop Offset="0.5" Color="{StaticResource PressedColorButtonLowerPartKey}" />
                                        </LinearGradientBrush.GradientStops>
                                    </LinearGradientBrush>
                                </Ellipse.Fill>
                            </Ellipse>
                            <Ellipse x:Name="InnerPressed" 
                                    Width="{Binding ElementName=Pressed, Path=Width}" Height="{Binding ElementName=Pressed, Path=Height}" 
                                    Stroke="DarkOrange" Opacity="0" StrokeThickness="1" SnapsToDevicePixels="True" Fill="Transparent"/>
                            <ContentPresenter Content="{TemplateBinding Button.Content}" HorizontalAlignment="Center" VerticalAlignment="Center">
                                <ContentPresenter.OpacityMask>
                                    <VisualBrush Visual="{Binding ElementName=ButtonControlBorder}" />
                                </ContentPresenter.OpacityMask>
                            </ContentPresenter>
                            <Grid.Triggers>
                                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                                    <BeginStoryboard x:Name="MouseEnterStoryboard">
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource ButtonUpperPartKey}" To="{StaticResource HooveredButtonUpperPartKey}" Duration="0:0:0.3" AutoReverse="False" />
                                            <ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource ButtonLowerPartKey}" To="{StaticResource HooveredButtonLowerPartKey}" Duration="0:0:0.3" />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource HooveredButtonUpperPartKey}" To="{StaticResource ButtonUpperPartKey}" Duration="0:0:1" AutoReverse="False" />
                                            <ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource HooveredButtonLowerPartKey}" To="{StaticResource ButtonLowerPartKey}" Duration="0:0:1" />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                            </Grid.Triggers>
                        </Grid>
                        <ControlTemplate.Resources>
                            <Storyboard x:Key="MouseUpTimeLine">
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                            <Storyboard x:Key="MouseDownTimeLine">
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="0.8" />
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                            <Storyboard x:Key="InnerPressedMouseUpTimeLine">
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                            <Storyboard x:Key="InnerPressedMouseDownTimeLine">
                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
                                    <SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1" />
                                </DoubleAnimationUsingKeyFrames>
                            </Storyboard>
                        </ControlTemplate.Resources>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" SourceName="Grid" Value="True">
                                <Setter Property="Stroke" TargetName="ButtonControlBorder">
                                    <Setter.Value>
                                        <SolidColorBrush Color="{StaticResource HooveredButtonLowerPartKey}">
                                        </SolidColorBrush>
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                            <Trigger Property="ButtonBase.IsPressed" Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource MouseDownTimeLine}" />
                                    <BeginStoryboard Storyboard="{StaticResource InnerPressedMouseDownTimeLine}">
                                    </BeginStoryboard>
                                </Trigger.EnterActions>
                                <Trigger.ExitActions>
                                    <BeginStoryboard Storyboard="{StaticResource MouseUpTimeLine}" />
                                    <BeginStoryboard Storyboard="{StaticResource InnerPressedMouseUpTimeLine}">
                                    </BeginStoryboard>
                                </Trigger.ExitActions>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
    1. BaseObservableObject is the simple implementation of INCP.

    2. RelayCommand is the simple implementation of ICommand interface.

    I'll glad to help if you will have the problem with the code. Regards,