La Güeb de Joaquín
WPF
WPF – Commands, Binding, y Multibinding
Sumario
Los comandos son un tipo especial de eventos. Son unos eventos abstractos y desacoplados de las interfaces de usuario. Reemplazan a los tradicionales eventos como el que se lanza cuando un usuario selecciona un elemento en una lista desplegable, y su característica más importante es que no están ligados a la interfaz de usuario que los expone.
Los enlaces de datos [Bindings] El enlace de datos es el proceso que establece una conexión entre la interfaz de usuario de la aplicación y la lógica del negocio. Si el enlace está correctamente configurado y los datos proporcionan las notificaciones adecuadas, cuando los datos cambian su valor, los elementos que están enlazados a ellos reflejan los cambios automáticamente. El enlace de datos también puede implicar la actualización automática de los datos que subyacen a una representación externa de los datos de un elemento, cuando esta representación cambia. Por ejemplo, si el usuario modifica el valor en un elemento TextBox, el valor de los datos subyacentes se actualiza automáticamente para reflejar ese cambio.
MultiBinding Describe una colección de objetos Binding asociados a una propiedad de destino de enlace. Permite enlazar una propiedad del destino de enlace a una lista de propiedades de origen y, a continuación, aplicar la lógica para generar un valor con las entradas indicadas. En este ejemplo de complemento se muestra cómo utilizar MultiBinding.
Historial de este documento
Autor Joaquin Medina Serrano http://joaquin.medina.name
Fechas importantes
· Creación.: sábado 15/Febrero/2007
· Modificación jueves, 29 de enero de 2015
· Impresión: jueves, 29 de enero de 2015/ 18:19
Contenido
2.2 Como se escribe un enlace de datos
2.3 Usar una clase [ValidationRule] para corregir errores de entrada
3.1 Ejemplo Un Command sin parámetros
3.2 Ejemplo (1) Un Command Con parámetros
3.3 Ejemplo (2) Un Command Con parámetros
4.2 Código con una clase converter
Este documento muestra como se usan los comandos [Command] el enlace de datos Binding, y el enlace múltiple de datos Multibinding
El documento se desarrolla sobre un ejercicio que consiste en una ventana que permite capturas horas, minutos y segundos, y con dos botones que son los que llamaran a los comandos.
Mas información en[https://msdn.microsoft.com/es-es/library/ms752347(v=vs.110).aspx]
El enlace de datos es el proceso que establece una conexión entre la interfaz de usuario de la aplicación y la lógica del negocio. Si el enlace está correctamente configurado y los datos proporcionan las notificaciones adecuadas, cuando los datos cambian su valor, los elementos que están enlazados a ellos reflejan los cambios automáticamente. El enlace de datos también puede implicar la actualización automática de los datos que subyacen a una representación externa de los datos de un elemento, cuando esta representación cambia. Por ejemplo, si el usuario modifica el valor en un elemento TextBox, el valor de los datos subyacentes se actualiza automáticamente para reflejar ese cambio.
Para escribir un enlace de datos hay que dar varios pasos
Escribir el modeloVista que se va a enlazar, En realidad es una clase que implementa la interfaz INotifyPropertyChanged
Ejemplo:
Imports System.ComponentModel
<Serializable>
Public Class TiempoModeloVista
Implements INotifyPropertyChanged
#Region "Evento PropertyChanged [Versión 2014-02-13]"
'-----------------------------------------------------------------------
' Declaracion del evento usando un EventHandler generico (Recomendada)
' !! Observacion !!! Se produce un error al serializar eventos Genericos
<NonSerializedAttribute()> _
Public Event PropertyChanged As _
System.ComponentModel.PropertyChangedEventHandler _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
'----------------------------------------------------------------------
''' <summary>Función que dispara el evento [PropertyChanged]</summary>
''' <param name="e">
''' Se espera un objeto del tipo [ComponentModel.PropertyChangedEventArgs]
''' Esta funcion se usa pra relanzar un evento
''' </param>
Protected Overloads Sub OnPropertyChanged(ByVal e As ComponentModel.PropertyChangedEventArgs)
If e Is Nothing Then
Throw New ArgumentException("No se admiten valores Nothing")
End If
Call OnPropertyChanged(e.PropertyName)
End Sub
'----------------------------------------------------------------------
''' <summary>Función que dispara el evento [PropertyChanged]</summary>
''' <param name="nombreDeLaPropiedadChanged">
''' <para>Se espera una cadena de texto con uno de los siguientes valores:</para>
''' <para> a) Una cadena vacía, en este caso la función averiguara el
''' nombre de la propiedad a través de su
''' atributo [CallerMemberName]]</para>
''' <para> b) Una cadena de texto con el nombre de
''' la propiedad (Ejemplo, "Apellidos") </para>
''' </param>
''' <remarks>
''' <code>
''' system.componentmodel.inotifypropertychanged(VS.95).aspx
''' http://msdn.microsoft.com/es-es/library/
''' Bibliografia [CallerMemberName>]
''' http://msdn.microsoft.com/es-es/library/hh534540.aspx
''' </code>
'''</remarks>
Protected Overloads Sub OnPropertyChanged(
<System.Runtime.CompilerServices.CallerMemberNameAttribute>
Optional ByVal nombreDeLaPropiedadChanged As String = Nothing)
' ----------------------------------
' Evitar problemas tontos
If String.IsNullOrWhiteSpace(nombreDeLaPropiedadChanged) = False Then
' Disparar el evento
RaiseEvent PropertyChanged( _
Me,
New System.ComponentModel.PropertyChangedEventArgs(nombreDeLaPropiedadChanged))
End If
End Sub
'
' /Eof - PropertyChanged
'-----------------------------------------------------------------------
#End Region
Private _horas As Integer
Private _minutos As Integer
Private _segundos As Integer
Public Property Horas() As Integer
Get
Return _horas
End Get
Set(ByVal value As Integer)
If _horas <> value Then
_horas = value
' --------------------------------------------------------
' Disparar el evento
Call OnPropertyChanged()
'' --------------------------------------------------------
End If
End Set
End Property
Public Property Minutos() As Integer
Get
Return _minutos
End Get
Set(ByVal value As Integer)
If _minutos <> value Then
_minutos = value
' --------------------------------------------------------
' Disparar el evento
Call OnPropertyChanged()
'' --------------------------------------------------------
End If
End Set
End Property
Public Property Segundos() As Integer
Get
Return _segundos
End Get
Set(ByVal value As Integer)
If _segundos <> value Then
_segundos = value
' --------------------------------------------------------
' Disparar el evento
Call OnPropertyChanged()
'' --------------------------------------------------------
End If
End Set
End Property
'' --------------------------------------------------------
Public Sub New()
' no hacer nada
End Sub
End Class
Declarar esta clase en la etiqueta [Window.Resources] de la vista
Primero declaro el namespace [local] en la etiqute [Window] de definición de la ventana
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PruebaHora"
Title="MainWindow" Height="173" Width="381">
Después escribo en [Window.Resources]
<Window.Resources>
<local:TiempoModeloVista x:Key="TiempoModeloVistaResources" />
</Window.Resources>
Enlazarlo con el control que queramos, hay dos formas de hacerlo
<TextBox HorizontalAlignment="Left" Height="23" Margin="316,60,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="35"
Text="{Binding Horas, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Source={StaticResource TiempoModeloVistaResources}}" />
<TextBox HorizontalAlignment="Left" Height="23" Margin="316,91,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="35" >
<TextBox.Text>
<Binding Source="{StaticResource TiempoModeloVistaResources}"
Path="Horas"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
</Binding>
</TextBox.Text>
</TextBox>
Si queremos comprobar que los datos de entrada son correctos, usaremos una case que herede de [] para relizar la comprobación
Escribimos la clase. En este caso quiero entrar horas y la calse comprueba quelos valores estén dento del rango permitido
Public Class HoraValidationRule
Inherits ValidationRule
Public Overloads Overrides Function Validate(value As Object, cultureInfo As Globalization.CultureInfo) As ValidationResult
Dim cadena As String = CType(value, String)
Dim numero As Integer
Try
numero = Integer.Parse(cadena)
If numero < 0 Then
Return New ValidationResult(False, "la hora no puede ser menor que cero ")
End If
If numero > 23 Then
Return New ValidationResult(False, "la hora no puede ser mayor de 23")
End If
Return New ValidationResult(True, Nothing)
Catch ex As Exception
Return New ValidationResult(False, "Problemas de conversión. solo se permiten numeros")
End Try
End Function
End Class
Para que esto funcione tenemos que declarar un estilo y unos [Trigger] que muestren visualmente un aviso de que los datos no son correctos. Se escriben en el nodo [Window.Resources] de la vista
<Window.Resources>
<local:TiempoModeloVista x:Key="TiempoModeloVistaResources" />
<local:TiempoModeloVistaConverter x:Key="TiempoModeloVistaConverterResources" />
<!-- Estilos que se aplican cuando hay un error -->
<Style x:Key="estilosParaErrorDelTextBox" TargetType="{x:Type TextBox}">
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Navy" />
</Trigger>
</Style.Triggers>
</Style>
<!-- Plantilla que simula un error provider -->
<ControlTemplate x:Key="templateSimularUnFormErrorProvider">
<DockPanel>
<!--<Label Foreground="Red" Background="Azure" Content="(!)" />-->
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
</Window.Resources>
Tercero,
engancharlos con el control textbox
<TextBox Name="TextBoxHoras" Margin="5" Width="25"
Style="{StaticResource estilosParaErrorDelTextBox}"
Validation.ErrorTemplate="{StaticResource templateSimularUnFormErrorProvider}"
VerticalAlignment="Top" MaxLength="2" >
<TextBox.Text>
<Binding Source="{StaticResource TiempoModeloVistaResources}"
Path="Horas"
UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay">
<Binding.ValidationRules>
<!-- Las reglas de validacion especificas de este dato -->
<local:HoraValidationRule />
<!-- Observa que no se emplean las reglas estándar
(que están comentadas a continuación)-->
<!--<ExceptionValidationRule />-->
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Un comando es una clase que implementa la interfaz [System.Windows.Input.ICommand]. Es decir que cualquier clase que implemente la interfaz ICoomand es un comando.
En WPF se usan para sustituir a los eventos
Mas información en
Información general sobre comandos [https://msdn.microsoft.com/es-es/library/ms752308(v=vs.110).aspx]
Un command se puede escribir en el ModeloVista, de hecho es allí donde los he encontrado siempre que he tenido que consultar documentación para aprender que era eso de los comandos J
Pero existe una forma mas elegante (para mi gusto) de escribir los comandos y es en una clase independiente. Esta forma de actuar planea el problema del código repetido, es decir tenemos que escribir una y otra vez el código necesario para la funcionalidad del comando, pero afortunadamente sabemos cómo resolver este problemilla y es crear una clase base donde se sitúa toda esa funcionalidad. En Internet existen muchas implementaciones pero a mí me gusta la siguiente:
Escribir la clase base con la funcionalidad ICommand
'/**
'--------------------------------------------------
' Version .....: 2015/01/28
' [aaaa/mm/dd]
' Original en .:
'--------------------------------------------------
'
' Clase .....: CommandBase
'
' Lenguaje ..: Visual Basic .NET (2012)
' Autor .....: Joaquin (jms32) Medina Serrano; en {AGUILA-joaquin}
' -------------------------------------------------
'*/
'
'----------------------------------------------------------
Option Explicit On
Option Strict On
Option Infer Off
Option Compare Binary
'------------------------------------------------------------
'
Imports System.Windows.Input ' para [ICommand]
''' <summary>
''' Define un comando.
''' </summary>
''' <remarks>
''' <example>
''' <code>How to implement a reusable ICommand</code>
''' <code>http://www.wpftutorial.net/DelegateCommand.html</code>
'''
''' Compartiendo Código: Móvil + Web + Escritorio (2/2)
''' <code>http://geeks.ms/blogs/jyeray/archive/2010/11/14/compartiendo-c-243-digo-m-243-vil-web-escritorio-2-2.aspx</code>
''' </example>
'''
''' <example>
''' <code language="vb" title="Forma de usar la función NEW">
''' <![CDATA[
'''
''' ' Instancia de la clase que implementa [Icommand]
''' Private _saveCommand As DelegateCommand
'''
''' <summary>
''' Devuelve un [Comando] que guarda un cliente en el repositorio
''' </summary>
''' Public ReadOnly Property SaveCommand() As ICommand
''' Get
''' If _saveCommand Is Nothing Then
''' '-------------------------------
''' ' Los delegados de [Commnad]
''' ' El [Delegado] (la Accion) que llamara al metodo [Save]
''' Dim saveAction As Action(Of Object) = New Action(Of Object)(AddressOf Me.Save)
''' ' El [Delegado] que llamara al metodo [CanSave]
''' Dim canExecuteAction As Predicate(Of Object) = Function(param) Me.CanSave
''' '-------------------------------
''' ' Instanciar el Command ---> NEW <----
''' _saveCommand = New DelegateCommand(saveAction, canExecuteAction)
''' '
''' End If
''' Return _saveCommand
''' End Get
''' End Property
'''
'''
''' Public Sub Save(ByVal parameters As Object)
''' ' Proceso omitido para simplificar el codigo
''' End Sub
'''
''' Private ReadOnly Property CanSave() As Boolean
''' Get
''' ' Proceso de validacion del cliente omitido
''' Return True
''' End Get
''' End Property
'''
''' ]]>
''' </code>
''' </example>
''' </remarks>
Public Class DelegateCommand
Implements ICommand, IDisposable
''' <summary>
''' Encapsula un método que no tiene parámetros y no devuelve un valor
''' Es el método que se tiene que ejecutar [Execute]
''' </summary>
Protected _canExecuteAction As Predicate(Of Object) = Nothing
''' <summary>
''' Encapsula un método que devuelve un valor Lógico
''' Es el método que devuelve el valor [CanExecute]
''' </summary>
Protected _executeAction As Action(Of Object) = Nothing
''' <summary>
''' El nombre del comando (si se declara)
''' Solo a efectos informativos
''' </summary>
Protected _nombreCommand As String
''' <summary>
''' El nombre del comando (si se declara)
''' Solo a efectos informativos
''' </summary>
Public ReadOnly Property Name As String
Get
Return _nombreCommand
End Get
End Property
Public ReadOnly Property GetCommand As ICommand
Get
Return Me
End Get
End Property
#Region "Constructores"
''' <summary>
''' Constructor para las clases derivadas únicamente
''' </summary>
Protected Sub New()
'Me.New(String.Empty, Nothing, Nothing)
End Sub
''' <summary>
''' Constructor
''' </summary>
''' <param name="pexecute">
''' <para> <see cref="Action(Of Object)">Action(Of Object)</see></para>
''' <para> Encapsula un método que no tiene parámetros y no devuelve un valor</para>
''' <para> Es el método que se tiene que ejecutar [Execute]</para>
''' </param>
Public Sub New(ByVal pexecute As Action(Of Object))
Me.New(String.Empty, pexecute, Nothing)
End Sub
''' <summary>
''' Constructor
''' </summary>
''' <param name="pname">
''' <para> <see cref="String">String</see></para>
''' <para> El nombre del comando. A efectos informativos únicamente</para>
''' </param>
''' <param name="pexecute">
''' <para> <see cref="Action(Of Object)">Action(Of Object)</see></para>
''' <para> Encapsula un método que no tiene parámetros
''' y no devuelve un valor</para>
''' <para> Es el método que se tiene que ejecutar [Execute]</para>
''' </param>
Public Sub New(ByVal pname As String, ByVal pexecute As Action(Of Object))
Me.New(pname, pexecute, Nothing)
End Sub
''' <summary>
''' Constructor
''' </summary>
''' <param name="pexecute">
''' <para> <see cref="Action(Of Object)">Action(Of Object)</see></para>
''' <para> Encapsula un método que no tiene parámetros
''' y no devuelve un valor</para>
''' <para> Es el método que se tiene que ejecutar [Execute]</para>
''' </param>
''' <param name="pcanExecute">
''' <para> <see cref="Predicate(Of Object)">Predicate(Of Object)</see></para>
''' <para> Encapsula un método que devuelve un valor Lógico</para>
''' <para> Es el método que devuelve el valor [CanExecute]</para>
''' </param>
Public Sub New(ByVal pexecute As Action(Of Object), ByVal pcanExecute As Predicate(Of Object))
Me.New(String.Empty, pexecute, pcanExecute)
End Sub
''' <summary>
''' Constructor
''' </summary>
''' <param name="pname">
''' <para> <see cref="String">String</see></para>
''' <para> El nombre del comando. A efectos informativos únicamente</para>
''' </param>
''' <param name="pexecute">
''' <para> <see cref="Action(Of Object)">Action(Of Object)</see></para>
''' <para> Encapsula un método que no tiene
''' parámetros y no devuelve un valor</para>
''' <para> Es el método que se tiene que ejecutar [Execute]</para>
''' </param>
''' <param name="pcanExecute">
''' <para> <see cref="Predicate(Of Object)">Predicate(Of Object)</see></para>
''' <para> Encapsula un método que devuelve un valor Lógico</para>
''' <para> Es el método que devuelve el valor [CanExecute]</para>
''' </param>
''' <remarks>
''' <example>
''' <para>Forma de usar la función NEW y parámetros que espera recibir </para>
'''
''' <code language="vb" title="Forma de usar la fucnion NEW">
''' <![CDATA[
'''
''' ' Instancia de la clase que implementa [Icommand]
''' Private _saveCommand As DelegateCommand
'''
''' <summary>
''' Devuelve un [Comando] que guarda un cliente en el repositorio
''' </summary>
''' Public ReadOnly Property SaveCommand() As ICommand
''' Get
''' If _saveCommand Is Nothing Then
''' '-------------------------------
''' ' Los delegados de [Commnad]
''' ' El [Delegado] (la Accion) que llamara al metodo [Save]
''' Dim saveAction As Action(Of Object) = New Action(Of Object)(AddressOf Me.Save)
''' ' El [Delegado] que llamara al metodo [CanSave]
''' Dim canExecuteAction As Predicate(Of Object) = Function(param) Me.CanSave
''' '-------------------------------
''' ' Instanciar el Command ---> NEW <----
''' _saveCommand = New DelegateCommand(saveAction, canExecuteAction)
''' '
''' End If
''' Return _saveCommand
''' End Get
''' End Property
'''
'''
''' Public Sub Save(ByVal parameters As Object)
''' ' Proceso omitido para simplificar el codigo
''' End Sub
'''
''' Private ReadOnly Property CanSave() As Boolean
''' Get
''' ' Proceso de validacion del cliente omitido
''' Return True
''' End Get
''' End Property
'''
''' ]]>
''' </code>
''' </example>
''' </remarks>
Public Sub New(ByVal pname As String,
ByVal pexecute As Action(Of Object),
ByVal pcanExecute As Predicate(Of Object))
MyBase.New()
Call CreateCommand(pname, pexecute, pcanExecute)
'Me._nombreCommand = pname
'Me._execute = pexecute
'Me._canExecute = pcanExecute
End Sub
''' <summary>
''' Carga los valores del Command.
''' Se usa por las clases derivadas
''' </summary>
''' <param name="pname">
''' <para> <see cref="String">String</see></para>
''' <para> El nombre del comando. A efectos informativos unicamente</para>
''' </param>
''' <param name="pexecute">
''' <para> <see cref="Action(Of Object)">Action(Of Object)</see></para>
''' <para> Encapsula un método que no tiene parámetros
''' y no devuelve un valor</para>
''' <para> Es el método que se tiene que ejecutar [Execute]</para>
''' </param>
''' <param name="pcanExecute">
''' <para> <see cref="Predicate(Of Object)">Predicate(Of Object)</see></para>
''' <para> Encapsula un método que devuelve un valor Lógico</para>
''' <para> Es el método que devuelve el valor [CanExecute]</para>
''' </param>
''' <remarks>
''' </remarks>
Protected Sub CreateCommand(ByVal pname As String, ByVal pexecute As Action(Of Object), ByVal pcanExecute As Predicate(Of Object))
Me._nombreCommand = pname
Me._executeAction = pexecute
Me._canExecuteAction = pcanExecute
End Sub
#End Region
Private canExecuteValorAnterior As Boolean = False
''' <summary>
''' Define el método que determina si el comando
''' se puede ejecutar en su estado actual.
''' </summary>
''' <param name="parameter">
''' <para>Tipo: <see cref="System.Object">System.Object</see> </para>
''' <para>Datos utilizados por el comando.</para>
''' <para>Si el comando no requiere datos para pasar, este objeto se puede
''' establecer en referencia null (Nothing en Visual Basic).</para>
''' </param>
''' <returns>
''' <para>Tipo: <see cref="System.Boolean">System.Boolean</see></para>
''' <para>true si este comando se puede ejecutar; si no, false.</para>
''' </returns>
''' <remarks>
''' <para>
''' Normalmente, un origen de comando llama al método de
''' CanExecute cuando se provoca el evento de CanExecuteChanged.
''' </para>
''' </remarks>
Public Function CanExecute(ByVal parameter As Object) As Boolean _
Implements System.Windows.Input.ICommand.CanExecute
Dim resultado As Boolean = False
If (_canExecuteAction Is Nothing) Then
resultado = True
Else
resultado = _canExecuteAction(parameter)
resultado = _canExecuteAction.Invoke(parameter)
End If
If canExecuteValorAnterior <> resultado Then
canExecuteValorAnterior = resultado
Call OnCanExecuteChanged()
End If
Return resultado
End Function
''' <summary>
''' Define el método que se llamará cuando se invoca el comando.
''' </summary>
''' <param name="parameter">
''' <para>Tipo: <see cref="System.Object">System.Object</see> </para>
''' <para>Datos utilizados por el comando.</para>
''' <para>Si el comando no requiere datos para pasar, este objeto se puede
''' establecer en referencia null (Nothing en Visual Basic).</para>
''' </param>
''' <remarks></remarks>
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
If _executeAction IsNot Nothing Then
_executeAction(parameter)
End If
End Sub
''' <summary>
''' Se dispara cuando se producen cambios que
''' afectan a si el comando se debe ejecutar.
''' </summary>
''' <remarks>
''' <para>
''' Normalmente, si no se puede ejecutar el comando,
''' el origen de comando se deshabilita.</para>
'''</remarks>
Public Custom Event CanExecuteChanged As EventHandler _
Implements ICommand.CanExecuteChanged
'Obligatorio. Los eventos declarados como Custom deben definir descriptores
'de acceso AddHandler, RemoveHandler y RaiseEvent personalizados.
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
''' <summary>
''' Dispara el evento [CanExecuteChanged]
''' </summary>
Public Sub OnCanExecuteChanged()
RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
End Sub
#Region "IDisposable Support"
Private _disposedValue As Boolean ' Para detectar llamadas redundantes
Protected Sub Dispose(disposing As Boolean)
If Not Me._disposedValue Then
If disposing Then
If Not (Me._executeAction Is Nothing) Then
Me._executeAction = Nothing
End If
If Not (Me._canExecuteAction Is Nothing) Then
Me._canExecuteAction = Nothing
End If
End If
Me._nombreCommand = Nothing
End If
Me._disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Escribir el Command específico, por ejemplo un Command que muestre un mensaje al pulsar un botón
Public Class CommandHola
Inherits DelegateCommand
Implements IDisposable
Public Sub New()
MyBase.New()
'-------------------------------
' Los parámetros de [Commnad]
'-------------------------------
' El [Delegado] (la Accion) que llamara al método [Execute]
Dim localExcuteAction As Action(Of Object) = New Action(Of Object)(AddressOf Me.Execute)
' El [Delegado] que llamara al método [CanExecute]
Dim localCanExecuteAction As Predicate(Of Object) = Function(param) Me.CanExecute(param)
' El nombre de este command (A efectos informativos)
'Dim localNombreCommand As String = "Hola Command"
' Solo el nombre de la clase
Dim localNombreCommand As String = System.Reflection.MethodBase.GetCurrentMethod.DeclaringType.Name
'-------------------------------
' Pasar los valores a la clase base
'-------------------------------
MyBase._nombreCommand = localNombreCommand
MyBase._executeAction = localExcuteAction
MyBase._canExecuteAction = localCanExecuteAction
End Sub
Public Overloads Sub Execute(parameter As Object)
MessageBox.Show("Hola amigo !!! (Con invocación del comando)")
End Sub
Public Overloads Function CanExecute(parameter As Object) As Boolean
Return True
End Function
#Region "IDisposable Support"
Private _disposedValue As Boolean ' Para detectar llamadas redundantes
' IDisposable
Protected Overloads Sub Dispose(disposing As Boolean)
If Not Me._disposedValue Then
If disposing Then
MyBase.Dispose()
End If
End If
Me._disposedValue = True
End Sub
#End Region
End Class
Declarar este Command en el apartado [Window.Resources]
<Window.Resources>
<local:CommandHola x:Key="CommandHolaResources" />
</Window.Resources>
Enganchar el Command al botón
<Button x:Name="ButtonHola" Content="Hola"
HorizontalAlignment="Left" Margin="5"
VerticalAlignment="Top" Width="75"
Command="{Binding Mode=OneWay,
Source={StaticResource CommandHolaResources}}"/>
En el parámetro de un Commad se puede pasar cualquier cosa, en este caso vamos a pasar el objeto [TiempoModeloVista] que lo tenemos enlazado a los controles con un Binding
Escribir el command , que lo que hará es colocar en los cuadros de texto hora, minutos y segundos la hora actual, tomada del sistema. Observa que el Command trabaja con las propiedades del objeto [TiempoModeloVista] y son estas propiedades las que actualiza, pero como están enlazadas [Binding] con cuadros TextBox, pues los cambios en sus valores se muestran también en la ventana principal
Public Class CommandAhora
Inherits DelegateCommand
Implements IDisposable
Public Sub New()
MyBase.New()
'-------------------------------
' Los parámetros de [Commnad]
'-------------------------------
' El [Delegado] (la Accion) que llamara al método [Execute]
Dim localExcuteAction As Action(Of Object) = New Action(Of Object)(AddressOf Me.Execute)
' El [Delegado] que llamara al método [CanExecute]
Dim localCanExecuteAction As Predicate(Of Object) = Function(param) Me.CanExecute(param)
' El nombre de este command (A efectos informativos)
'Dim localNombreCommand As String = "Delete TextBox"
' solo el nombre de la clase
Dim localNombreCommand As String = System.Reflection.MethodBase.GetCurrentMethod.DeclaringType.Name
'-------------------------------
' Pasar los valores a la clase base
'-------------------------------
MyBase._nombreCommand = localNombreCommand
MyBase._executeAction = localExcuteAction
MyBase._canExecuteAction = localCanExecuteAction
End Sub
Public Overloads Sub Execute(parameter As Object)
' Recuperar el objeto pasado por parámetro
Dim objTiempo As TiempoModeloVista
objTiempo = TryCast(parameter, TiempoModeloVista)
Dim ahora As DateTime = DateTime.Now
objTiempo.Horas = ahora.Hour
objTiempo.Minutos = ahora.Minute
objTiempo.Segundos = ahora.Second
End Sub
Public Overloads Function CanExecute(parameter As Object) As Boolean
Return True
End Function
#Region "IDisposable Support"
Private _disposedValue As Boolean ' Para detectar llamadas redundantes
' IDisposable
Protected Overloads Sub Dispose(disposing As Boolean)
If Not Me._disposedValue Then
If disposing Then
MyBase.Dispose()
End If
' liberar recursos no administrados (objetos no administrados) e invalidar Finalize() below.
' Establecer campos grandes como Null.
End If
Me._disposedValue = True
End Sub
#End Region
End Class
Ejemplo, queremos escribir un Command que borre un cuadro de texto. Evidentemente, el command estará enlazado con (por ejemplo) un botón y cuando se pulse se borrara el cuadro de texto. En este caso el valor que se recibe por parámetro en un control [TextBox]
''' <summary>
''' Borra el contenido de un TextBox
''' </summary>
Public NotInheritable Class TextBoxDeleteCommand
Inherits DelegateCommand
Implements IDisposable
Public Sub New()
MyBase.New()
'-------------------------------
' Los parámetros de [Commnad]
' El [Delegado] (la Accion) que llamara al metodo [cmdExecute]
Dim localExcuteAction As Action(Of Object) = New Action(Of Object)(AddressOf Me.cmdExecute)
' El [Delegado] que llamara al metodo [cmdCanExecute]
Dim localCanExecuteAction As Predicate(Of Object) = Function(param) Me.cmdCanExecute(param)
' el nombre de este command
Dim localNombreCommand As String = "Delete TextBox"
'-------------------------------
MyBase._nombreCommand = localNombreCommand
MyBase._execute = localExcuteAction
MyBase._canExecute = localCanExecuteAction
End Sub
''' <summary>
''' Esta función Borra todo el texto de un textBox pasado por parámetro
''' </summary>
Private Sub cmdExecute(ByVal parameter As Object)
If parameter IsNot Nothing Then
Dim target As TextBox = TryCast(parameter, TextBox)
If target IsNot Nothing Then
If target.Text.Length > 0 Then
target.Clear()
End If
End If
End If
End Sub
''' <summary>
''' Esta función Comprueba que en TextBox haya un texto para poder ejecutar la función [Execute]
''' Si hay un texto devuelve (True) [Si se puede ejecutar], si no hay texto devuelve false [No se puede ejecutar]
''' </summary>
Private ReadOnly Property cmdCanExecute(ByVal parameter As Object) As Boolean
Get
Dim resultado As Boolean = False
If parameter IsNot Nothing Then
Dim target As TextBox = TryCast(parameter, TextBox)
If target IsNot Nothing Then
If target.Text.Length > 0 Then
resultado = True
End If
End If
End If
Return resultado
End Get
End Property
#Region "IDisposable Support"
Private _disposedValue As Boolean ' Para detectar llamadas redundantes
' IDisposable
Protected Overloads Sub Dispose(disposing As Boolean)
If Not Me._disposedValue Then
If disposing Then
MyBase.Dispose()
End If
' liberar recursos no administrados (objetos no administrados) e invalidar Finalize() below.
' Establecer campos grandes como Null.
End If
Me._disposedValue = True
End Sub
#End Region
End Class
<UserControl.Resources>
<!--el comando [Borrar un TetxBox]-->
<local:TextBoxDeleteCommand x:Key="TextBoxDeleteCommandResources" />
</UserControl.Resources>
El TextBox que vamos a borrar
<TextBox Grid.Column="0" x:Name="TextBoxInterno" />
<Button x:Name="ButtonBorrarUri"
Grid.Column="1"
HorizontalAlignment="Right" VerticalAlignment="Top" Width="20"
Command="{Binding Mode=OneWay, Source={StaticResource TextBoxDeleteCommandResources}}"
CommandParameter="{Binding ElementName=TextBoxInterno, Mode=OneWay}"
CommandTarget="{Binding ElementName=TextBoxInterno, Mode=OneWay}">
<Button.Content>
<Image Source="action_Cancel_16xLG_BN.png"
Stretch="None" />
</Button.Content>
</Button>
[https://msdn.microsoft.com/es-es/library/System.Windows.Data.MultiBinding(v=vs.110).aspx]
Problema Como puedo presentar en un TextBloc información sobre la hora que se ha introducido, es decir, mostrar la hora, minutos y segundos.
NOTA.: se puede definir una propiedad en el [ModeloVIsta] que proporcione esta información formateada y hacer un enlace de datos a un Control para mostrarla, pero lo que quiero hacer en este ejemplo es usar [MultiBinding ]
Recordemos que tengo tres controles TextBox, que contienen la hora, los minutos y los segundos respectivamente, y no puedo hacer un Binding con los tres objetos ¿no?. La solución MultiBinding que consiste en hacer varios Binding a varios controles diferentes a la vez
El código básico es el siguiente:
<Window.Resources>
<local:TiempoModeloVista x:Key="TiempoModeloVistaResources" />
</Window.Resources>
<!--https://msdn.microsoft.com/es-es/library/System.Windows.Data.MultiBinding(v=vs.110).aspx-->
<TextBlock x:Name="MultiBindingEjemploUno"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
FontWeight="Bold" Margin="28,44,91,73"
Background="#FFDCCB24"
DataContext="{StaticResource TiempoModeloVistaResources}">
<TextBlock.Text>
<MultiBinding Mode="OneWay"
StringFormat="Hora, {0:00}:{1:00}:{2:00}">
<Binding Path="Horas"/>
<Binding Path="Minutos" />
<Binding Path="Segundos" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Observa que
· tengo que declarar la etiqueta [DataContext] si no, no funciona
· se enlazan las propiedades del ModeloVista, no los controles d(TextBox) de la vista
· La etiqueta [StringFormat] formatea la salida
También se puede usar una clase Converter para formatear los datos
Observa que se sale de lo ‘acostumbrado’ porque implementa [IMultiValueConverter]
Public Class TiempoModeloVistaConverter
Implements IMultiValueConverter
Public Function Convert(ByVal values() As Object,
ByVal targetType As Type,
ByVal parameter As Object,
ByVal culture As System.Globalization.CultureInfo) As Object _
Implements IMultiValueConverter.Convert
Dim horas As Integer = CInt(values(0))
Dim minutos As Integer = CInt(values(1))
Dim segundos As Integer = CInt(values(2))
Dim resultado As String = String.Empty
Select Case CStr(parameter)
Case "Completa"
resultado = String.Format(culture,
"{0:00}:{1:00}:{2:00}",
horas, minutos, segundos)
Case "Sencilla"
resultado = String.Format(culture,
"{0:00}{1:00}{2:00}",
horas, minutos, segundos)
Case Else
resultado = String.Format(culture,
"{0:00}{1:00}{2:00}",
horas, minutos, segundos)
End Select
Return resultado
End Function
Public Function ConvertBack(ByVal value As Object,
ByVal targetTypes() As Type,
ByVal parameter As Object,
ByVal culture As Globalization.CultureInfo) As Object() _
Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException("NO se usa, no se iplementa")
End Function
End Class
<Window.Resources>
<local:TiempoModeloVista x:Key="TiempoModeloVistaResources" />
<local:TiempoModeloVistaConverter x:Key="TiempoModeloVistaConverterResources" />
</Window.Resources>
Que esta enlazado con [MultiBinding]
<!--https://msdn.microsoft.com/es-es/library/System.Windows.Data.MultiBinding(v=vs.110).aspx-->
<TextBlock x:Name="MultiBindingEjemploDos"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontWeight="Bold" Margin="28,74,91,42"
Background="Beige"
DataContext="{StaticResource TiempoModeloVistaResources}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TiempoModeloVistaConverterResources}"
ConverterParameter="Completa"
Mode="OneWay">
<Binding Path="Horas"/>
<Binding Path="Minutos" />
<Binding Path="Segundos" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>