Pergunta

Eu tenho um EmployeeViewModel classe com 2 propriedades "Nome" e "Sobrenome". A classe também tem um dicionário com as mudanças das propriedades. (Os implementos classe INotifyPropertyChanged e IDataErrorInfo, está tudo bem.

Na minha opinião, não é uma caixa de texto:

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

Como posso alterar a cor da caixa de texto fundo, se o valor original mudou? Pensei em criar um gatilho que define a cor de fundo, mas o que eu ligam deveria? Eu não quero criar uma propriedade adicional para cada controle que detém o estado wheter aquele foi alterado ou não.

Thx

Foi útil?

Solução

Basta usar um MultiBinding com a mesma propriedade duas vezes, mas tem Mode = OneTime em uma das ligações. Como esta:

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

E no XAML:

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

Nenhuma propriedade extra ou lógica necessária e você provavelmente poderia embrulhar tudo em sua própria extensão de marcação. Espero que ajude.

Outras dicas

Você vai precisar usar um conversor de valor (convertendo string de entrada para a saída de cores) e a solução mais simples envolve a adição de mais pelo menos uma propriedade para o seu EmployeeViewModel. Você precisa fazer algum tipo de Padrão ou OriginalValue propriedade, e comparar com isso. Caso contrário, como você vai saber o que o "valor original" era? Você não pode dizer se o valor alterado a menos que há algo mantendo o valor original para comparação.

Então, se ligam à propriedade de texto e comparar a cadeia de entrada para o valor original sobre o modelo de vista. Se tiver mudado, voltar a sua cor de fundo realçado. Se ele corresponder, retorne a cor de fundo normal. Você vai precisar usar um multi-ligação, se você quiser comparar o FirstName e LastName em conjunto a partir de uma única caixa de texto.

eu construí um exemplo que demonstra como isso poderia funcionar:

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

E aqui está o código-behind para a janela:

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

    public Window11()
    {
        InitializeComponent();
    }
}

Finalmente, aqui é o conversor que você 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();
    }
}

E mesmo que eu envolto a borda ao redor do TextBox (porque eu acho que se parece um pouco melhor), a ligação fundo pode ser feito exatamente da mesma maneira:

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

Se você estiver usando o paradigma MVVM, você deve considerar os ViewModels como tendo o papel de adaptadores entre o modelo ea vista.

Não se espera do ViewModel para ser completamente agnóstico da existência de uma interface de usuário em todos os sentidos, mas para ser agnóstico de qualquer específica UI.

Assim, o ViewModel pode (e deve) ter a funcionalidade de tantos conversores quanto possível. O exemplo prático aqui seria esta:

que um UI precisa saber se um texto é igual a uma string padrão?

Se a resposta for Sim , é razão suficiente para implementar uma propriedade IsDefaultString em um 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
    }
}

Em seguida, ligam o TextBox assim:

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

Esta propaga texto volta para o ViewModel em cima foco TextBox perder, o que provoca um novo cálculo da IsTextDefault. Se você precisar fazer isso um monte de vezes, ou para muitas propriedades, você pode até mesmo cozinhar alguma classe base como DefaultManagerViewModel.

Você pode adicionar aos seus propriedades boolean ViewModel como IsFirstNameModified e IsLastNameModified, e usar um gatilho para mudar o fundo, se a caixa de texto de acordo com essas propriedades. Ou você pode vincular a Background a essas propriedades, com um conversor que retorna um Brush de um bool ...

Uma maneira diferent completa seria a de não implementar INotifyPropertyChanged e em vez descendem de DependencyObject ou UIElement

Eles implementar a ligação usando DependencyProperty Você pode usar evento apenas um manipulador de eventos e e.Property usuário a encontrar o rigth caixa de texto

Eu tenho certeza que a e.NewValue! = Verificação e.OldValue é redundante, já que a ligação não deve ter mudado. Eu também acredito que pode haver uma maneira de implementar a ligação de modo que o dependecyObject é a caixa de texto e não o seu objeto ...

Editar se você já herdar de qualquer classe WPF (como o controle ou usercontrol) provavelmente você está ok e você não precisa mudar para UIElement como a maioria de WPF herdam essa classe

Em seguida, você pode ter:

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

    }

 }
}

Uma variação da última resposta poderia ser a de estar sempre no estado modificado a menos que o valor é o valor padrão.

 <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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top