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

1.1         Introducción. 2

2       Binding. 3

2.1         Que es un enlace de datos. 3

2.2         Como se escribe un enlace de datos. 3

2.3         Usar una clase [ValidationRule] para corregir errores de entrada. 6

3       La interfaz ICommand. 8

3.1         Ejemplo Un Command sin parámetros. 15

3.2         Ejemplo (1) Un Command Con parámetros. 16

3.3         Ejemplo (2) Un Command Con parámetros. 18

4       Multibinding. 20

4.1         Código básico. 20

4.2         Código con una clase converter. 21

 

 

 

1.1       Introducción

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.

 

 

2         Binding

2.1       Que es un enlace de datos

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.

2.2       Como se escribe un enlace de datos

Para escribir un enlace de datos hay que dar varios pasos

Primero

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

 

Segundo

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>

 

Tercero

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>

 

2.3       Usar una clase [ValidationRule] para corregir errores de entrada

 

Si queremos comprobar que los datos de entrada son correctos, usaremos una case que herede de [] para relizar la comprobación

 

Primero

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

 

Segundo

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>

 

 

3         La interfaz ICommand

 

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:

 

Primero

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

 

3.1       Ejemplo Un Command sin parámetros

 

Primero

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

 

Segundo

Declarar este Command en el apartado [Window.Resources]

<Window.Resources>

        <local:CommandHola x:Key="CommandHolaResources" />

</Window.Resources>

Tercero

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}}"/>

 

3.2       Ejemplo (1) Un Command Con parámetros

 

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

Primero

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

 

3.3       Ejemplo (2) Un Command Con parámetros

 

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]

Primero el Command

 

 

''' <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

 

Segundo, Declararlo en Window Resources de la Vista

 

    <UserControl.Resources>

        <!--el comando [Borrar un TetxBox]-->

        <local:TextBoxDeleteCommand x:Key="TextBoxDeleteCommandResources" />

    </UserControl.Resources>

 

 

Tercero el Botón

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>

 

 

4         Multibinding

[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

 

4.1       Código básico

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

 

4.2       Código con una clase converter

También se puede usar una clase Converter para formatear los datos

Primero la clase [Converter]

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

 

Segundo La declaracion [Window.Resources]

    <Window.Resources>

        <local:TiempoModeloVista x:Key="TiempoModeloVistaResources" />

        <local:TiempoModeloVistaConverter x:Key="TiempoModeloVistaConverterResources" />

    </Window.Resources>

 

Tercero El código del control [TextBlock]

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>