J'ai lu un certain nombre d'articles sur la liaison et les commandes, mais j'ai du mal à faire fonctionner ce que je veux.
Ce qui suit fonctionne bien
<Window x:Class="ESL_Master_Suite.Components.Core.Resources.TailoredReading"
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:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
xmlns:this1="clr-namespace:ESL_Master_Suite.Components.Controls"
xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
mc:Ignorable="d"
Title="TailoredReading" WindowStartupLocation="CenterScreen" Width="1024">
<Window.DataContext>
<this:ViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{x:Static this:Commands.myRoutingCommand}" Key="F1" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="{x:Static this:Commands.myRoutingCommand}" CanExecute="myRoutingCommandCanExecute" Executed="myRoutingCommandExecuted"/>
</Window.CommandBindings>
public class Commands
{
public static readonly RoutedUICommand myRoutingCommand = new RoutedUICommand("myCommand", "myCommand", typeof(InputGestureWindow));
void myRoutingCommandExecuted(object target, ExecutedRoutedEventArgs e)
{
String command = ((RoutedCommand)e.Command).Name;
MessageBox.Show("The \"" + command + "\" command has been invoked. ");
}
void myRoutingCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
4 Réponses :
Lors de la liaison des commandes en XAML: je pense que c'est parce que les méthodes liées à CanExecute et Executed doivent être des membres d'instance du code derrière la classe.
Si vous voulez faire ce que vous faites, vous pouvez par exemple le faire dans le constructeur statique dans le code:
public class Commands
{
public static RoutedCommand MyCommand = new RoutedCommand("MyCommand", typeof(TailoredReading ));
static Commands()
{
CommandBinding commandBinding = new CommandBinding(MyCommand, MyCommandCmd_Executed, MyCommandCmd_CanExecute);
CommandManager.RegisterClassCommandBinding(typeof(TailoredReading ), commandBinding);
}
public static void MyCommandCmd_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static void MyCommandCmd_Executed(object sender, ExecutedRoutedEventArgs e)
{
}
}
Je ne sais pas si le problème est résolu? Ce sont mes suggestions pour le résoudre. S'il n'est pas nécessaire d'utiliser une RoutedUICommand , changez-la en une propre classe appelée RelayCommand (Lien: RelayCommand implementation ) qui dérivent de ICommand . Ensuite, votre classe Commands ressemble à ceci:
xmlns:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources" xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
Dans votre TailoredReading Window XAML-File, vous aviez ajouté l'espace de noms où vous avez localisé le classer. Dans mon exemple, il s'agit de clr-namespace: WpfAppNet.Commands (comme la première ligne du code extrait). Si vous l'avez déjà ajouté à l'alias d'espace de nom, vous n'avez pas besoin de le faire.
Dans vos extraits de code, veuillez regarder si vous avez ajouté votre fichier Commands.cs em> et TailoredReading.cs dans le dossier Resources . Sinon, cela pourrait être la raison de votre erreur.
<Window x:Class="WpfAppNet.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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfAppNet"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
<!-- add an namespace alias where the file Commands.cs is located -->
xmlns:commands="clr-namespace:WpfAppNet.Commands"
Title="MainWindow" Height="450" Width="800">
<Window.InputBindings>
<!-- Bind to the command -->
<KeyBinding Command="{x:Static commands:Commands.MyRoutingCommand}" Key="F1" />
</Window.InputBindings>
...
</Window>
L'avantage de la classe RelayCommand est d'éviter l'implémentation de Execute et CanExecute pour chaque nouvelle commande. Vous avez seulement besoin d'implémenter les méthodes / fonctions pour eux.
PS: J'ai également vu que vous avez ajouté deux fois le même chemin pour différents alias d'espace de noms:
namespace WpfAppNet.Commands
{
public class Commands
{
public static ICommand MyRoutingCommand = new RelayCommand(MyRoutingCommandExecuted, (o) =>
{
return MyRoutingCommandCanExecute();
});
private static void MyRoutingCommandExecuted(object target)
{
MessageBox.Show("The command has been invoked. ");
}
private static bool MyRoutingCommandCanExecute()
{
return true;
}
}
}
La logique de vos commandes n'a rien à voir avec l'utilisation des données, il est donc inutile de l'implémenter dans le ViewModel.
Votre classe Commands n'est pas un ViewModel, une classe d'assistance qui fait partie de la vue.
L'extension de balisage "x: Static", pour autant que je sache, peut obtenir la valeur des constantes, des énumérations ou des champs et propriétés STATIC.
Mais pas la MÉTHODE CONVENTIONNELLE!
Essayez cette implémentation:
<Window.InputBindings>
<KeyBinding Command="{x:Static this:Commands.MyRoutingCommand}" Key="F1" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="{x:Static this:Commands.MyRoutingCommand}"
CanExecute="{x:Static this:Commands.MyRoutingCommandCanExecute}"
Executed="{x:Static this:Commands.MyRoutingCommandExecuted}"/>
</Window.CommandBindings>
XAML:
public static class Commands
{
public static RoutedUICommand MyRoutingCommand { get; } = new RoutedUICommand("myCommand", "myCommand", typeof(Commands));
public static ExecutedRoutedEventHandler MyRoutingCommandExecuted { get; }
= myRoutingCommandExecuted;
private static void myRoutingCommandExecuted(object target, ExecutedRoutedEventArgs e)
{
string command = ((RoutedCommand)e.Command).Name;
MessageBox.Show("The \"" + command + "\" command has been invoked. ");
}
public static CanExecuteRoutedEventHandler MyRoutingCommandCanExecute { get; }
= myRoutingCommandCanExecute;
private static void myRoutingCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
}
Et, bien sûr, assurez-vous que les espaces de noms sont corrects .
Une fois les modifications apportées à XAML, un avertissement d'erreur peut s'afficher.
Mais cela est dû au fait que les informations pour XAML ne sont pas extraites du projet, mais de l'assembly.
Par conséquent, les erreurs devraient disparaître après la construction du projet modifié.
Hé, j'ai essayé cette solution mais j'ai rencontré une exception: System.Windows.Markup.XamlParseException: '' Définir la propriété 'System.Windows.Input.CommandBinding.CanExecute' a levé une exception. ' ArgumentException: l'objet de type 'System.Action`2 [System.Object, System.Windows.Input.CanExecu teRoutedEventArgs]' ne peut pas être converti en type 'System.Windows.Input.CanExecuteRoutedEventHandler'.
Cela s'est produit dans: public Tailored Reading () {InitializeComponent (); }
Pardon! Inattention montrée. J'ai corrigé l'erreur dans le code. Le type de délégués doit avoir été défini explicitement, et non via Action <...>.
myRoutingCommandCanExecute et myRoutingCommandExecuted sont des gestionnaires d'événements. Vous ne pouvez pas les définir dans une autre classe.
En fait, utiliser une RoutedUICommand n'est pas très utile si vous voulez séparer votre logique d'exeuction de la vue. Veuillez consulter ce billet de blog pour plus d'informations à ce sujet.
Ce que vous devez faire est de créer une classe personnalisée qui implémente ICommand et accepte une Action et un Predicate:
<Window x:Class="ESL_Master_Suite.Components.Core.Resources.TailoredReading"
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:this="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
xmlns:this1="clr-namespace:ESL_Master_Suite.Components.Controls"
xmlns:local="clr-namespace:ESL_Master_Suite.Components.Core.Resources"
mc:Ignorable="d"
Title="TailoredReading" WindowStartupLocation="CenterScreen" Width="1024">
<Window.DataContext>
<this:ViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="F1" />
</Window.InputBindings>
</Window>
Vous créez ensuite des instances de la commande dans votre modèle de vue où vous pouvez également définir la logique d'exécution:
public class ViewModel
{
public ViewModel()
{
MyCommand = new DelegateCommand(MyCommandExecuted, MyCommandCanExecute);
}
public DelegateCommand MyCommand { get; }
private void MyCommandExecuted(object obj)
{
MessageBox.Show("The command has been invoked.");
}
private bool MyCommandCanExecute(object obj)
{
return true;
}
}
La vue se lie alors à la propriété de commande du modèle de vue:
public class DelegateCommand : System.Windows.Input.ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public event EventHandler CanExecuteChanged;
}
Évidemment, vous n'avez pas à implémenter l 'Action et le Predicate que vous passez à la commande dans la classe de modèle de vue. Vous pouvez les mettre en œuvre où vous le souhaitez.
Merci pour votre réponse et pour le billet de blog qui a été vraiment utile.
Publiez également les espaces de noms XAML pour la fenêtre.
Soit le titre de la question est incorrect, soit il s'agit d'une implémentation très étrange. Il s'agit d'une classe statique distincte qui n'est pas un modèle de vue. Mais si vous essayez le code de Henrik, il devrait être local: Commands.MyCommand En supposant que votre code correspond au sien, c'est. À moins que cela ne soit spécifique à cette fenêtre, vous souhaiterez probablement Window plutôt que TailoredReading comme type auquel la liaison de commande sera associée. Si c'est spécifique à la fenêtre, placer vos gestionnaires d'événements dans une classe distincte semble très étrange.
Je suis désolé. Je ne sais pas vraiment comment cela devrait être configuré. J'ai la fenêtre comme ci-dessus. J'ai une classe ViewModel avec toutes les données de l'interface utilisateur. À l'origine, je voulais ajouter la logique de commande à la classe View Model, mais je me suis ensuite demandé pourquoi ne pas la garder séparée dans sa propre classe. Je ne sais pas quelle est la bonne ou la meilleure façon.