style control wpf xaml mvvm wpf-controls data-binding

style - user control wpf



¿Cómo puedo enlazar los comandos de WPF entre un UserControl y una ventana principal? (3)

Comenzaré dejando que una imagen hable un poco.

Así que ya ves, quiero crear un control de usuario de WPF que admita el enlace a DataContext de una ventana principal. El control del usuario es simplemente un botón y un cuadro de lista con una plantilla de elementos personalizada para presentar cosas con una etiqueta y un botón Eliminar.

El botón Agregar debe llamar a ICommand en el modelo de vista principal para interactuar con el usuario al seleccionar una cosa nueva (instancia de IThing). Los botones Eliminar en el ListBoxItem en el control del usuario deberían llamar de manera similar a un ICommand en el modelo de vista principal para solicitar la eliminación del objeto relacionado. Para que eso funcione, el botón Eliminar tendría que enviar alguna información de identificación al modelo de vista sobre lo que se solicita que se elimine. Así que hay 2 tipos de comandos que deben ser vinculables a este control. Algo como AddThingCommand () y RemoveThingCommand (cosa de IThing).

Conseguí que la funcionalidad funcionara utilizando los eventos Click, pero eso me parece extraño, produciendo un montón de código detrás del XAML, y roza el resto de la implementación MVVM prístina. Realmente quiero usar Comandos y MVVM normalmente.

Hay suficiente código involucrado para hacer que funcione una demostración básica. Estoy a la espera de publicar todo para reducir la confusión. Lo que funciona que me hace sentir que estoy tan cerca es que DataTemplate para ListBox vincula la etiqueta correctamente, y cuando la ventana principal agrega elementos a la colección, aparecen.

<Label Content="{Binding Path=DisplayName}" />

Si bien eso muestra el IThing correctamente, el botón Eliminar justo al lado no hace nada cuando lo hago clic.

<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">

Esto no es terriblemente inesperado, ya que el elemento específico no se proporciona, pero el botón Agregar no tiene que especificar nada, y tampoco puede ejecutar el comando.

<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">

Entonces, lo que necesito es la solución "básica" para el botón Agregar, de modo que llame al comando de la ventana primaria para agregar una cosa, y la solución más compleja para el botón Eliminar, de modo que también llame al comando principal pero también pase Su cosa encuadernada.

Muchas gracias por cualquier idea,


Consulte el siguiente código. UserControl.XAML

<Grid> <ListBox ItemsSource="{Binding Things}" x:Name="lst"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding ThingName}" Margin="3"/> <Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>

Ventana.Xaml

<Window x:Class="MultiBind_Learning.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MultiBind_Learning" Title="Window1" Height="300" Width="300"> <StackPanel Orientation="Horizontal"> <Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/> <local:UserControl2/> </StackPanel>

Window.xaml.cs

public partial class Window1 : Window { public Window1() { InitializeComponent(); this.DataContext = new ThingViewModel(); } }

ThingViewModel.cs

class ThingViewModel { private ObservableCollection<Thing> things = new ObservableCollection<Thing>(); public ObservableCollection<Thing> Things { get { return things; } set { things = value; } } public ICommand AddCommnd { get; set; } public ICommand RemoveCommand { get; set; } public ThingViewModel() { for (int i = 0; i < 10; i++) { things.Add(new Thing() { ThingName="Thing" +i}); } AddCommnd = new BaseCommand(Add); RemoveCommand = new BaseCommand(Remove); } void Add(object obj) { things.Add(new Thing() {ThingName="Added New" }); } void Remove(object obj) { things.Remove((Thing)obj); } }

Cosa.cs

class Thing :INotifyPropertyChanged { private string thingName; public string ThingName { get { return thingName; } set { thingName = value; OnPropertyChanged("ThingName"); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } }

BaseCommand.cs

public class BaseCommand : ICommand { private Predicate<object> _canExecute; private Action<object> _method; public event EventHandler CanExecuteChanged; public BaseCommand(Action<object> method) { _method = method; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { _method.Invoke(parameter); } }

En lugar del comando Base, puede probar RelayCommand desde MVVMLight o DelegateCommand desde las bibliotecas PRISM.


Esto es trivial, y se hace al tratar a su UserControl como lo que es: un control (que simplemente se compone de otros controles). Qué significa eso? Esto significa que debe colocar DependencyProperties en su UC a la que se puede enlazar su ViewModel, como cualquier otro control. Los botones exponen una propiedad de Comando, TextBoxes exponen una propiedad de Texto, etc. Necesita exponer, en la superficie de su UserControl, todo lo que necesita para que haga su trabajo.

Tomemos un ejemplo trivial (lanzados juntos en menos de dos minutos). Dejaré fuera la implementación de ICommand.

Primero nuestra ventana

<Window x:Class="UCsAndICommands.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:t="clr-namespace:UCsAndICommands" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <t:ViewModel /> </Window.DataContext> <t:ItemsEditor Items="{Binding Items}" AddItem="{Binding AddItem}" RemoveItem="{Binding RemoveItem}" /> </Window>

Observe que tenemos nuestro editor de elementos, que expone las propiedades para todo lo que necesita: la lista de elementos que está editando, un comando para agregar un nuevo elemento y un comando para eliminar un elemento.

A continuación, el UserControl

<UserControl x:Class="UCsAndICommands.ItemsEditor" 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:t="clr-namespace:UCsAndICommands" x:Name="root"> <UserControl.Resources> <DataTemplate DataType="{x:Type t:Item}"> <StackPanel Orientation="Horizontal"> <Button Command="{Binding RemoveItem, ElementName=root}" CommandParameter="{Binding}">Remove</Button> <TextBox Text="{Binding Name}" Width="100"/> </StackPanel> </DataTemplate> </UserControl.Resources> <StackPanel> <Button Command="{Binding AddItem, ElementName=root}">Add</Button> <ItemsControl ItemsSource="{Binding Items, ElementName=root}" /> </StackPanel> </UserControl>

Enlazamos nuestros controles a los DP definidos en la superficie de la UC. Por favor, no hagas tonterías como DataContext=this; Como este anti-patrón rompe implementaciones UC más complejas.

Aquí están las definiciones de estas propiedades en la UC

public partial class ItemsEditor : UserControl { #region Items public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register( "Items", typeof(IEnumerable<Item>), typeof(ItemsEditor), new UIPropertyMetadata(null)); public IEnumerable<Item> Items { get { return (IEnumerable<Item>)GetValue(ItemsProperty); } set { SetValue(ItemsProperty, value); } } #endregion #region AddItem public static readonly DependencyProperty AddItemProperty = DependencyProperty.Register( "AddItem", typeof(ICommand), typeof(ItemsEditor), new UIPropertyMetadata(null)); public ICommand AddItem { get { return (ICommand)GetValue(AddItemProperty); } set { SetValue(AddItemProperty, value); } } #endregion #region RemoveItem public static readonly DependencyProperty RemoveItemProperty = DependencyProperty.Register( "RemoveItem", typeof(ICommand), typeof(ItemsEditor), new UIPropertyMetadata(null)); public ICommand RemoveItem { get { return (ICommand)GetValue(RemoveItemProperty); } set { SetValue(RemoveItemProperty, value); } } #endregion public ItemsEditor() { InitializeComponent(); } }

Solo DPs en la superficie de la UC. No biggie Y nuestro ViewModel es igualmente simple

public class ViewModel { public ObservableCollection<Item> Items { get; private set; } public ICommand AddItem { get; private set; } public ICommand RemoveItem { get; private set; } public ViewModel() { Items = new ObservableCollection<Item>(); AddItem = new DelegatedCommand<object>( o => true, o => Items.Add(new Item())); RemoveItem = new DelegatedCommand<Item>( i => true, i => Items.Remove(i)); } }

Está editando tres colecciones diferentes, por lo que es posible que desee exponer más comandos de IC para dejar en claro qué está agregando / eliminando. O bien, podría obtener un descuento y usar el CommandParameter para resolverlo.


Por defecto, su control de usuario heredará el DataContext de su contenedor. Por lo tanto, la clase ViewModel que utiliza su ventana puede vincularse directamente con el control del usuario, utilizando la notación de vinculación en XAML. No es necesario especificar DependentProperties o RoutedEvents, simplemente vincular a las propiedades del comando de la forma habitual.