Question

J'ai une classe EmployeeViewModel avec 2 propriétés " Prénom " et "Nom". La classe dispose également d'un dictionnaire avec les modifications des propriétés. (La classe implémente INotifyPropertyChanged et IDataErrorInfo, tout va bien.

À mon avis, il existe une zone de texte:

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

Comment puis-je changer la couleur d'arrière-plan de la zone de texte si la valeur d'origine a changé? J'ai pensé créer un déclencheur qui définit la couleur de fond, mais à quoi dois-je me lier? Je ne veux pas créer une propriété supplémentaire pour chaque contrôle qui conserve l'état, que celui-ci ait été modifié ou non.

Thx

Était-ce utile?

La solution

Utilisez simplement une liaison multiple avec la même propriété deux fois mais Mode = OneTime sur l’une des liaisons. Comme ceci:

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

Et dans le xaml:

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

Aucune propriété ou logique supplémentaire n'est requise et vous pouvez probablement envelopper le tout dans votre propre extension de balisage. J'espère que ça aide.

Autres conseils

Vous devrez utiliser un convertisseur de valeur (conversion de l’entrée chaîne en sortie couleur) et la solution la plus simple consiste à ajouter au moins une propriété supplémentaire à votre EmployeeViewModel. Vous devez créer une sorte de propriété Par défaut ou OriginalValue , et la comparer à celle-ci. Sinon, comment saurez-vous quelle est la "valeur d'origine"? était? Vous ne pouvez pas savoir si la valeur a changé à moins que quelque chose ne contienne la valeur d'origine à comparer.

Alors, liez-vous à la propriété text et comparez la chaîne d'entrée à la valeur d'origine du modèle de vue. Si cela a changé, retournez la couleur de fond surlignée. Si cela correspond, retourne la couleur d'arrière-plan normale. Vous devrez utiliser une liaison multiple si vous souhaitez comparer Prénom et Nom à partir d'une seule zone de texte.

J'ai construit un exemple qui montre comment cela pourrait fonctionner:

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

Et voici le code-behind de la fenêtre:

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

    public Window11()
    {
        InitializeComponent();
    }
}

Enfin, voici le convertisseur que vous utilisez:

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();
    }
}

Et même si j’ai enroulé une bordure autour de la zone de texte (parce que cela me semble un peu mieux), la liaison en arrière-plan peut être faite exactement de la même manière:

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

Si vous utilisez le paradigme de MVVM, vous devez considérer les ViewModels comme ayant le rôle d'adaptateurs entre le modèle et la vue.

On ne s'attend pas à ce que ViewModel soit totalement agnostique quant à l'existence d'une interface utilisateur, mais à une agnostique vis-à-vis de toute interface utilisateur spécifique .

Ainsi, le ViewModel peut (et devrait) avoir les fonctionnalités du plus grand nombre possible de convertisseurs. L’exemple pratique ici serait le suivant:

Une interface utilisateur aurait-elle besoin de savoir si un texte correspond à une chaîne par défaut?

Si la réponse est oui , il est suffisant d'implémenter une propriété IsDefaultString sur 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
    }
}

Liez ensuite la TextBox comme ceci:

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

Cela renvoie le texte au ViewModel lors de la perte de focus de TextBox , ce qui provoque un recalcul de IsTextDefault . Si vous avez besoin de le faire souvent ou pour de nombreuses propriétés, vous pouvez même créer une classe de base telle que DefaultManagerViewModel .

Vous pouvez ajouter à vos propriétés booléennes ViewModel telles que IsFirstNameModified et IsLastNameModified , et utiliser un déclencheur pour modifier l'arrière-plan du champ en fonction de ces propriétés. Vous pouvez également associer Arrière-plan à ces propriétés, avec un convertisseur renvoyant un Pinceau à partir d'un booléen ...

Une autre façon de procéder serait de ne pas implémenter INotifyPropertyChanged mais plutôt de descendre de DependencyObject ou de UIElement

Ils implémentent la liaison à l'aide de DependencyProperty Vous pouvez utiliser un seul gestionnaire d’événements et utilisateur e.Property pour rechercher la zone de texte exacte

.

Je suis presque sûr que la vérification e.NewValue! = e.OldValue est redondante car la liaison n'aurait pas dû être modifiée. Je pense aussi qu'il peut y avoir un moyen d'implémenter la liaison, donc dependecyObject est la zone de texte et non votre objet ...

Modifiez si vous héritez déjà d'une classe WPF (comme control ou usercontrol), vous êtes probablement d'accord et vous n'avez pas besoin de changer pour UIElement car la plupart des WPF héritent de cette classe

Ensuite, vous pouvez avoir:

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

    }

 }
}

Une variante de la dernière réponse pourrait être de toujours être à l'état modifié, à moins que la valeur ne soit la valeur par défaut.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top