Pregunta

Tengo una clase EmployeeViewModel con 2 propiedades " Nombre " y " Apellido " ;. La clase también tiene un diccionario con los cambios de las propiedades. (La clase implementa INotifyPropertyChanged e IDataErrorInfo, todo está bien.

En mi opinión, hay un cuadro de texto:

<TextBox x:Name="firstNameTextBox" Text="{Binding Path=FirstName}" />

¿Cómo puedo cambiar el color de fondo del cuadro de texto, si el valor original cambió? Pensé en crear un disparador que establezca el color de fondo, pero ¿a qué debo unir? No quiero crear una propiedad adicional para cada control que mantenga el estado en el que se modificó o no.

Thx

¿Fue útil?

Solución

Simplemente use un MultiBinding con la misma propiedad dos veces pero tenga Mode = OneTime en uno de los enlaces. Así:

Public Class MVCBackground
    Implements IMultiValueConverter

    Public Function Convert(ByVal values() As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
        Static unchanged As Brush = Brushes.Blue
        Static changed As Brush = Brushes.Red

        If values.Count = 2 Then
            If values(0).Equals(values(1)) Then
                Return unchanged
            Else
                Return changed
            End If
        Else
            Return unchanged
        End If
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetTypes() As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

Y en el xaml:

<TextBox Text="{Binding TestText}">
    <TextBox.Background>
        <MultiBinding Converter="{StaticResource BackgroundConverter}">
            <Binding Path="TestText"    />
            <Binding Path="TestText" Mode="OneTime" />
        </MultiBinding>
    </TextBox.Background>
</TextBox>

No se requieren propiedades ni lógica adicionales y probablemente podría incluirlo todo en su propia extensión de marcado. Espero que ayude.

Otros consejos

Deberá usar un convertidor de valor (convertir la entrada de cadena en salida de color) y la solución más simple implica agregar al menos una propiedad más a su EmployeeViewModel. Debe hacer algún tipo de propiedad Predeterminado u OriginalValue , y comparar con eso. De lo contrario, ¿cómo sabrá cuál es el "valor original"? ¿estaba? No puede saber si el valor cambió a menos que haya algo que contenga el valor original para compararlo.

Por lo tanto, enlace a la propiedad de texto y compare la cadena de entrada con el valor original en el modelo de vista. Si ha cambiado, devuelva el color de fondo resaltado. Si coincide, devuelve el color de fondo normal. Deberá utilizar un enlace múltiple si desea comparar el Nombre y el Apellido juntos desde un solo cuadro de texto.

He construido un ejemplo que demuestra cómo podría funcionar esto:

<Window x:Class="TestWpfApplication.Window11"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="Window11" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
    <local:ChangedDefaultColorConverter x:Key="changedDefaultColorConverter"/>
</Window.Resources>
<StackPanel>
    <StackPanel Orientation="Horizontal">
        <TextBlock>Default String:</TextBlock>
        <TextBlock Text="{Binding Path=DefaultString}" Margin="5,0"/>
    </StackPanel>
    <Border BorderThickness="3" CornerRadius="3"
            BorderBrush="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}">
        <TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"/>
    </Border>
</StackPanel>

Y aquí está el código subyacente para la ventana:

/// <summary>
/// Interaction logic for Window11.xaml
/// </summary>
public partial class Window11 : Window
{
    public static string DefaultString
    {
        get { return "John Doe"; }
    }

    public Window11()
    {
        InitializeComponent();
    }
}

Finalmente, aquí está el convertidor que usa:

public class ChangedDefaultColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string text = (string)value;
        return (text == Window11.DefaultString) ?
            Brushes.Transparent :
            Brushes.Yellow;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Y aunque envolví un borde alrededor del TextBox (porque creo que se ve un poco mejor), el enlace de fondo se puede hacer exactamente de la misma manera:

<TextBox Name="textBox" Text="{Binding Path=DefaultString, Mode=OneTime}"
         Background="{Binding ElementName=textBox, Path=Text, Converter={StaticResource changedDefaultColorConverter}}"/>

Si está utilizando el paradigma MVVM, debe considerar que los ViewModels tienen el rol de adaptadores entre el Modelo y la Vista.

No se espera que ViewModel sea completamente independiente de la existencia de una IU en todos los sentidos, sino que sea independiente de cualquier IU específica .

Entonces, ViewModel puede (y debería) tener la funcionalidad de tantos convertidores como sea posible. El ejemplo práctico aquí sería este:

¿Necesitaría una interfaz de usuario saber si un texto es igual a una cadena predeterminada?

Si la respuesta es , es una razón suficiente para implementar una propiedad IsDefaultString en un ViewModel.

public class TextViewModel : ViewModelBase
{
    private string theText;

    public string TheText
    {
        get { return theText; }
        set
        {
            if (value != theText)
            {
                theText = value;
                OnPropertyChanged("TheText");
                OnPropertyChanged("IsTextDefault");
            }
        }
    }

    public bool IsTextDefault
    {
        get
        {
            return GetIsTextDefault(theText);
        }
    }

    private bool GetIsTextDefault(string text)
    {
        //implement here
    }
}

Luego enlace el TextBox de esta manera:

<TextBox x:Name="textBox" Background="White" Text="{Binding Path=TheText, UpdateSourceTrigger=LostFocus}">
    <TextBox.Resources>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsTextDefault}" Value="False">
                    <Setter Property="TextBox.Background" Value="Red"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBox.Resources>
</TextBox>

Esto propaga el texto nuevamente al ViewModel cuando TextBox pierde el foco, lo que provoca un nuevo cálculo del IsTextDefault . Si necesita hacer esto muchas veces o para muchas propiedades, incluso podría preparar alguna clase base como DefaultManagerViewModel .

Puede agregar a sus propiedades booleanas de ViewModel como IsFirstNameModified y IsLastNameModified , y usar un activador para cambiar el fondo del cuadro de texto de acuerdo con estas propiedades. O podría vincular el Fondo a estas propiedades, con un convertidor que devuelva un Brush de un bool ...

Una forma completamente diferente sería no implementar INotifyPropertyChanged y, en su lugar, descender de DependencyObject o UIElement

Implementan el enlace usando DependencyProperty Puede utilizar solo un controlador de eventos y usuario e.Property para encontrar el cuadro de texto correcto

Estoy bastante seguro de que la verificación e.NewValue! = e.OldValue es redundante ya que el enlace no debería haber cambiado. También creo que puede haber una manera de implementar el enlace, por lo que dependecyObject es el cuadro de texto y no su objeto ...

Edite si ya hereda de cualquier clase de WPF (como control o control de usuario) probablemente esté bien y no necesite cambiar a UIElement ya que la mayoría de WPF hereda de esa clase

Entonces puedes tener:

using System.Windows;
namespace YourNameSpace
{
class PersonViewer:UIElement
{

    //DependencyProperty FirstName
    public static readonly DependencyProperty FirstNameProperty =
        DependencyProperty.Register("FirstName", typeof (string), typeof (PersonViewer),
                                    new FrameworkPropertyMetadata("DefaultPersonName", FirstNameChangedCallback));

    public string FirstName {
        set { SetValue(FirstNameProperty, value); }
        get { return (string) GetValue(FirstNameProperty); }
    }

    private static void FirstNameChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {

        PersonViewer owner = d as PersonViewer;
        if (owner != null) {
            if(e.NewValue != e.OldValue && e.NewValue != "DefaultPersonName" ) {

                //Set Textbox to changed state here

            }
        }

    }

    public void AcceptPersonChanges() {

        //Set Textbox to not changed here

    }

 }
}

Una variación de la última respuesta podría ser estar siempre en el estado modificado a menos que el valor sea el valor predeterminado.

 <TextBox.Resources>
    <Style TargetType="{x:Type TextBox}">

        <Style.Triggers>
            <Trigger Property="IsLoaded" Value="True">
                <Setter Property="TextBox.Background" Value="Red"/>
            </DataTrigger>
        </Style.Triggers>

        <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource Self}, Path=Text" Value="DefaultValueHere">
                <Setter Property="TextBox.Background" Value=""/>
            </DataTrigger>
        </Style.Triggers>

    </Style>
</TextBox.Resources>

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top