none
Usercontrol : Binder une commande sur une dependency property RRS feed

  • Discussion générale

  • Bonjour à tous,

    Je pinaille depuis quelques jours sur le Binding d'une Command avec une DependencyProperty que j'ai crée sur un UserControl (contenant un bouton).

    Ci-dessous mon UserControl XAML :

    <UserControl x:Class="Default.Views.Controls.TestButton"
                 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" 
                 xmlns:local="clr-namespace:Default.Views.Controls"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
            <Button Content="{Binding Content}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
        </Grid>
    </UserControl>

    Ci-dessous le code behind du UserControl :

    public partial class TestButton : UserControl
        {
            #region Properties
    
            // Content of the button
            public static readonly new DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(string), typeof(TestButton));
            public new string Content
            {
                get { return (string)GetValue(ContentProperty); }
                set { SetValue(ContentProperty, value); }
            }
    
            // Command of the button
            public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(TestButton));
            public ICommand Command
            {
                get { return (ICommand)GetValue(CommandProperty); }
                set { SetValue(CommandProperty, value); }
            }
    
            // Command parameters of the button
            public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(Object[]), typeof(TestButton));
            public object[] CommandParameter
            {
                get { return (object[])GetValue(CommandParameterProperty); }
                set { SetValue(CommandParameterProperty, value); }
            }
    
            #endregion
    
            #region Methods
            public TestButton()
            {
                InitializeComponent();
                this.DataContext = this;
            }
    
            #endregion
        }

    Ensuite j'appelle le UserControl dans mon MainWindow, et je Bind les les propriétés Command et CommandParameter avec le ViewModel du MainWindow.


    Ci-dessous mon MainWindow XAML :

    <Window x:Class="Default.Views.Windows.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Default.Views.Windows"
            xmlns:controls="clr-namespace:Default.Views.Controls"
            xmlns:pages="clr-namespace:Default.Views.Pages"
            xmlns:vm="clr-namespace:Default.ViewModels.Pages"
            mc:Ignorable="d"
            Title="MainWindow" Height="550" Width="900" MinHeight="550" MinWidth="900">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="95"/>
                <RowDefinition Height="1"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="20"/>
            </Grid.RowDefinitions>
    
            <!-- Headband bouttons -->
            <Grid Grid.Row="0">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="70"/>
                    <ColumnDefinition Width="70"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
    
                <!-- Test usercontrol button -->
                <controls:TestButton Grid.Column="0" Content="Usercontrol" Command="{Binding ChangeViewCommand}">
                    <controls:TestButton.CommandParameter>
                        <x:Array xmlns:sys="clr-namespace:System;assembly=mscorlib" Type="sys:Object">
                            <x:Type Type="pages:Settings"/>
                            <x:Type Type="vm:SettingsVM"/>
                        </x:Array>
                    </controls:TestButton.CommandParameter>
                </controls:TestButton>
                
                <!-- Test normal button -->
                <Button Grid.Column="1" Content="Normal" Command="{Binding ChangeViewCommand}">
                    <Button.CommandParameter>
                        <x:Array xmlns:sys="clr-namespace:System;assembly=mscorlib" Type="sys:Object">
                            <x:Type Type="pages:Settings"/>
                            <x:Type Type="vm:SettingsVM"/>
                        </x:Array>
                    </Button.CommandParameter>
                </Button>
            </Grid>
    
            <!-- Headband separator -->
            <Separator Grid.Row="1" Style="{StaticResource MenuSeparator}" />
    
            <!-- Content frame (display according with button pressed) -->
            <Frame Grid.Row="2" Content="{Binding CurrentPage}" />
    
            <!-- Message bar -->
            <controls:MessageBar Grid.Row="3" Message="Mon message..." />
    
        </Grid>
    </Window>


    Ci-dessous le ViewModel de mon MainWindow :

    class MainWindowVM<TPage> : BaseNotifyPropertyChanged
        {
            #region Properties
            public object CurrentPage
            {
                get { return GetProperty<object>(); }
                set { SetProperty<object>(value); }
            }
    
            #endregion
    
            #region Commands
    
            public BaseCommand<Type, Type> ChangeViewCommand
            {
                get => new BaseCommand<Type, Type>(/*async*/(tView, tViewModel) => ChangeView(tView, tViewModel));
            }
    
            #endregion
    
            #region Methods
            public MainWindowVM(Type defaultPageType)
            {
                this.CurrentPage = (TPage)NavigationService.GetView<HomeVM>(defaultPageType);
            }
    
            public void ChangeView(Type tView, Type tViewModel)
            {
                this.CurrentPage = NavigationService.GetView(tView, tViewModel);
            }
    
            #endregion
        }


    Je vous donne également les classes NavigationService.cs, BaseNotifyPropertyChanged.cs, et BaseCommand.cs. Que j'utilise respectivement pour la navigation entre mes fenêtres, pour implémenter l'interface INotifyPropertyChanged par héritage, et pour implémenter l'interface ICommand par héritage.

    Ci-dessous mon NavigationService :

    public static class NavigationService
        {
            public static string ContextPropertyName = "DataContext";
    
            private static Dictionary<Type, object> _viewsCache =
                new Dictionary<Type, object>();
            private static Dictionary<Type, BaseNotifyPropertyChanged> _viewModelsCache =
                new Dictionary<Type, BaseNotifyPropertyChanged>();
    
            private static TViewModel GetViewModelInstance<TViewModel>(params object[] viewModelParameters)
                where TViewModel : BaseNotifyPropertyChanged
            {
                return (TViewModel)GetViewModelInstance(typeof(TViewModel), viewModelParameters);
            }
            private static object GetViewModelInstance(Type tViewModel, params object[] viewModelParameters)
            {
                object vm = null;
                if (_viewModelsCache.ContainsKey(tViewModel))
                    vm = _viewModelsCache[tViewModel];
                else
                {
                    vm = Activator.CreateInstance(tViewModel, viewModelParameters);
                    _viewModelsCache[tViewModel] = (BaseNotifyPropertyChanged)vm;
                }
                return vm;
            }
    
            private static TView GetViewInstance<TView>(object viewModel)
                where TView : class
            {
                return (TView)GetViewInstance(typeof(TView), viewModel);
            }
            private static object GetViewInstance(Type tView, object viewModel)
            {
                object view = null;
                bool isWindow = tView.BaseType.Name == "Window";
                if (!isWindow && _viewsCache.ContainsKey(tView))
                    view = _viewsCache[tView];
                else
                {
                    view = Activator.CreateInstance(tView);
                    var prop = tView.GetProperty(ContextPropertyName);
                    prop?.SetValue(view, viewModel);
                    if (!isWindow)
                        _viewsCache[tView] = view;
                }
                return view;
            }
    
            public static TView GetView<TView, TViewModel>(params object[] viewModelParameters)
                where TView : class
                where TViewModel : BaseNotifyPropertyChanged
            {
                return GetViewInstance<TView>(
                    GetViewModelInstance<TViewModel>(viewModelParameters));
            }
            public static object GetView<TViewModel>(Type tView, params object[] viewModelParameters)
                where TViewModel : BaseNotifyPropertyChanged
            {
                return GetViewInstance(
                    tView,
                    GetViewModelInstance<TViewModel>(viewModelParameters));
            }
            public static object GetView(Type tView, Type tViewModel, params object[] viewModelParameters)
            {
                return GetViewInstance(
                    tView,
                    GetViewModelInstance(tViewModel, viewModelParameters));
            }
    
            public static void Show<TView, TViewModel>(params object[] viewModelParameters)
                where TView : class
                where TViewModel : BaseNotifyPropertyChanged
            {
                var win = GetViewInstance<TView>(
                    GetViewModelInstance<TViewModel>(viewModelParameters));
    
                var method = win.GetType().GetMethod("Show");
                method?.Invoke(win, null);
            }
            public static void Show<TViewModel>(Type tView, params object[] viewModelParameters)
                where TViewModel : BaseNotifyPropertyChanged
            {
                var win = GetViewInstance(
                    tView,
                    GetViewModelInstance<TViewModel>(viewModelParameters));
    
                var method = win.GetType().GetMethod("Show");
                method?.Invoke(win, null);
            }
            public static void Show(Type tView, Type tViewModel, params object[] viewModelParameters)
            {
                var win = GetViewInstance(
                    tView,
                    GetViewModelInstance(tViewModel, viewModelParameters));
    
                var method = win.GetType().GetMethod("Show");
                method?.Invoke(win, null);
            }
    
            public static bool? ShowDialog<TView, TViewModel>(params object[] viewModelParameters)
                where TView : class
                where TViewModel : BaseNotifyPropertyChanged
            {
                var win = GetViewInstance<TView>(
                    GetViewModelInstance<TViewModel>(viewModelParameters));
    
                var method = win.GetType().GetMethod("ShowDialog");
                return (bool?)method?.Invoke(win, null);
            }
            public static bool? ShowDialog<TViewModel>(Type tView, params object[] viewModelParameters)
                where TViewModel : BaseNotifyPropertyChanged
            {
                var win = GetViewInstance(
                    tView,
                    GetViewModelInstance<TViewModel>(viewModelParameters));
    
                var method = win.GetType().GetMethod("ShowDialog");
                return (bool?)method?.Invoke(win, null);
            }
            public static bool? ShowDialog(Type tView, Type tViewModel, params object[] viewModelParameters)
            {
                var win = GetViewInstance(
                    tView,
                    GetViewModelInstance(tViewModel, viewModelParameters));
    
                var method = win.GetType().GetMethod("ShowDialog");
                return (bool?)method?.Invoke(win, null);
            }
    
            public static void Close(object view, bool? result = null)
            {
                if (result != null)
                {
                    var property = view.GetType().GetProperty("DialogResult");
                    property?.SetValue(view, result);
                }
                var method = view.GetType().GetMethod("Close");
                method?.Invoke(view, null);
            }
    
            public static bool? GetResult(object view)
            {
                var property = view.GetType().GetProperty("DialogResult");
                return (bool?)property?.GetValue(view);
            }
        }


    Ci-dessous BaseNotifyPropertyChanged :

    public abstract class BaseNotifyPropertyChanged : INotifyPropertyChanged
        {
    
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private Dictionary<string, object> _propertyValues = new Dictionary<string, object>();
    
            protected T GetProperty<T>([CallerMemberName] string propertyName = null)
            {
                if (_propertyValues.ContainsKey(propertyName)) return (T)_propertyValues[propertyName];
                return default(T);
            }
    
            protected bool SetProperty<T>(T newValue, [CallerMemberName] string propertyName = null)
            {
                T current = GetProperty<T>(propertyName);
                if (!EqualityComparer<T>.Default.Equals(current, newValue))
                {
                    _propertyValues[propertyName] = newValue;
                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                    }
                    return true;
                }
                return false;
            }
            
        }

    Ci-dessous BaseCommand :

        public class BaseCommand<T1, T2> : ICommand
        {
            private readonly Action<T1, T2> _execute;
            private readonly Func<T1, T2, bool> _canExecute;
    
            public event EventHandler CanExecuteChanged;
    
            public BaseCommand(Action<T1, T2> execute) : this(execute, null) { }
    
            public BaseCommand(Action<T1, T2> execute, Func<T1, T2, bool> canExecute)
            {
                if (execute == null)
                {
                    throw new ArgumentNullException("execute");
                }
    
                _execute = execute;
                _canExecute = canExecute;
            }
    
    
            public void OnCanExecuteChanged()
            {
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }
    
            public bool CanExecute(object parameter)
            {
                T1 param1;
                T2 param2;
                GetParameters(parameter, out param1, out param2);
    
                return _canExecute == null || _canExecute(param1, param2);
            }
    
            public void Execute(object parameter)
            {
                if (CanExecute(parameter) && _execute != null)
                {
                    T1 param1;
                    T2 param2;
                    GetParameters(parameter, out param1, out param2);
                    _execute(param1, param2);
                }
            }
    
            private void GetParameters(object parameter, out T1 param1, out T2 param2)
            {
                param1 = default(T1);
                param2 = default(T2);
    
                if (parameter == null || parameter.GetType() != typeof(object[]) || ((object[])parameter).Length != 2) return;
    
                param1 = (T1)((object[])parameter)[0];
                param2 = (T2)((object[])parameter)[1];
            }
        }

    Au final, lorsque je clique sur le bouton "Normal" dans MainWindow je passe bien par un point d'arrêt dans ma fonction ChangeView, et la frame se met bien à jour.

    Mais si je clique sur le bouton "Usercontrol", je ne passe pas par mon point d'arrêt dans ma fonction ChangeView.

    Merci d'avance pour votre aide (et désolé pour les conventions de nommage que je ne respecte pas toujours). :)





    dimanche 15 novembre 2020 14:30