11
votes

WPF Reliure et attribution de manière dynamique de la propriété StringFormat

J'ai un formulaire généré en fonction de plusieurs éléments de daTembles. L'un des éléments de DataTemplate crée une zone de texte d'une classe qui ressemble à ceci: xxx

J'ai besoin d'un moyen de "lier" la valeur dans la propriété de formatage de la propriété "StringFormat" du obligatoire. Jusqu'à présent, j'ai: xxx

Cependant, étant donné que StringFormat n'est pas une propriété de dépendance, je ne peux pas le lier.

Ma prochaine pensée était de créer Un convertisseur de valeur et transmettez la valeur de la propriété de formatage dans le converterParameter, mais j'ai couru dans le même problème - ConverterParameter n'est pas une dépendanceProperty.

Alors, maintenant je me tourne vers vous, alors. Comment définir dynamiquement le stringformat d'une liaison? Plus spécifiquement, sur une zone de texte?

Je préférerais laisser Xaml faire le travail pour moi afin que je puisse éviter de jouer avec le code-derrière. J'utilise le motif MVVM et j'aimerais garder les limites entre le modèle de visualisation et la vision comme non floue que possible.

Merci!


0 commentaires

5 Réponses :


2
votes

Une manière peut être de créer une classe qui hérite textbox et dans cette classe Créez votre propre propriété de dépendance qui déléguette à stringformat lors de la définition. Ainsi, au lieu d'utiliser textbox dans votre XAML, vous utiliserez la zone de texte héritée et définissez votre propre propriété de dépendance dans la liaison.


2 commentaires

C'est une bonne suggestion. Je vais devoir examiner cela. J'étais un peu en espérant qu'il y aurait une solution qui n'impliquait pas des contrôles personnalisés, mais je suis certainement ouvert à cela. Je reviendrai après un peu de recherche.


J'essaie de faire la même chose, mais je ne sais pas comment configurer les propriétés ci-jointes pour gérer cela. J'ai posté une nouvelle question: Stackoverflow.com/q/24119097/65461



1
votes

Il suffit de lier la zone de texte à l'instance d'une myTextboxClass au lieu de myTextboxClass.value et utilisez un ValueConverter pour créer une chaîne de la valeur et de la mise en forme.

Une autre solution consiste à utiliser un convertisseur de multivalue qui se lierait à la fois à la valeur et au formatage.

La première solution ne prend pas en charge les modifications apportées aux propriétés, c'est-à-dire si la valeur ou la formatage change le convertisseur de valeur ne sera pas appelée comme si vous utilisez un multivéeconverter et une liaison directement sur les propriétés.


3 commentaires

La liaison à l'instance MyTextboxClass est quelque chose que j'ai essayé, mais la méthode de recto-enregistrement dans l'encéconnet sera un problème car il y en a beaucoup, de nombreuses propriétés que je n'ai pas de place pour un objet Textbox. Donc, je recevrais un objet incomplet revenir de la zone de texte. Je vais regarder dans le convertisseur multi-valeurs. Cependant, la formatage n'est pas liée car il s'agit d'une propriété de dépendance, alors je ne suis pas sûr que ça va travailler.


Comment est-ce censé travailler? Lorsque la zone de texte est mise à jour en utilisant la base de données, le texte est formaté à l'aide de la formatage. Lorsqu'un utilisateur met à jour la zone de texte, il peut entrer n'importe quel texte pouvant être incompatible avec la mise en forme de la formatage. Est-ce que ça va? Êtes-vous sûr de ne pas vouloir utiliser une zone de texte masquée à la place? En outre, la mise en forme est aussi liée que toute autre propriété publique.


"La formatrice est aussi bien liée que toute autre propriété publique" Expliquez pourquoi, alors vous obtiendrez une erreur indiquant que "une" liaison "ne peut pas être définie sur la propriété" StringFormat "de type" Binding ". Une" liaison "ne peut être définie que sur une dépendance spécifique d'une dépendanceObject. "



1
votes

On pourrait créer un comportement attaché qui pourrait remplacer la liaison avec celui qui a le FormatString spécifié. Si la propriété de dépendance FormatString alors la liaison serait à nouveau être mis à jour. Si la liaison est mis à jour alors la FormatString serait à nouveau appliquée que la liaison.

Les deux seules choses délicates que je peux penser que vous auriez à traiter. Une question est de savoir si vous voulez créer deux propriétés attachées qui coordonnent entre eux pour le FormatString et le TargetProperty sur lequel la fixation exist que le FormatString doit être appliqué (ex. TextBox.Text) ou peut-être vous pouvez simplement supposer que la propriété de votre transaction avec selon le type de commande cible. L'autre question est peut-être qu'il peut être non trivial de copier une liaison existante et de modifier légèrement compte tenu des différents types de liaisons là-bas qui pourrait également inclure des liaisons personnalisées.

Il est important de considérer que si tous cela ne permette d'obtenir le formatage dans la direction de vos données à votre contrôle. Pour autant que je peux découvrir en utilisant quelque chose comme un MultiBinding avec une coutume MultiValueConverter à consommer à la fois la valeur initiale et la FormatString et produire la sortie désirée souffre toujours du même problème surtout parce que la méthode ConvertBack est seulement donné la chaîne de sortie et que vous souhaitez attendre à déchiffrer à la fois la FormatString et la valeur d'origine de celle-ci qui, à ce moment-là est presque toujours impossible.

Les solutions restantes qui devraient fonctionner pour le formatage bidirectionnel et unformatting serait le suivant:

  • Ecrire un contrôle personnalisé qui étend TextBox a suggéré le comportement désiré comme le formatage Jakob Christensen.
  • Ecrire un convertisseur de valeur personnalisée qui dérive de DependencyObject soit ou FrameworkElement et a une DependencyProperty FormatString sur elle. Si vous voulez aller la route DependencyObject je crois que vous pouvez pousser la valeur dans la propriété FormatString en utilisant la OneWayToSource liaison avec une technique de « branche virtuelle ». L'autre moyen plus facile peut hériter de la place à FrameworkElement et placez votre convertisseur de valeur dans l'arbre visuel avec vos autres contrôles afin que vous pouvez simplement lier à lui en cas de besoin par ElementName.
  • Utilisez un comportement attaché similaire à celui je l'ai mentionné au sommet de ce poste, mais au lieu de fixer un lieu FormatString ont deux propriétés attachées, un pour un convertisseur de valeur personnalisée et un pour le paramètre qui serait transmis au convertisseur de valeur . Ensuite, au lieu de modifier l'original liant pour ajouter le FormatString vous ajouterez le convertisseur et le paramètre de conversion à la liaison. Personnellement, je pense que cette option entraînerait le résultat le plus lisible et intuitive parce que les comportements attachés ont tendance à être plus assez propre mais encore flexible pour être utilisé dans une variété d'autres situations que juste une zone de texte.

0 commentaires

3
votes

Ce code (inspiré de defaultvalueconverter.cs @ Références.microsoft.com ) fonctionne pour une liaison à deux voies à une zone de texte ou à un contrôle similaire, à condition que la formatrice quitte la version de ToString () de la propriété source dans un état pouvant être converti. (c.-à-d. format comme "#, 0,00" est correct car "1,234.56" peut être analysé, mais la formatrineString = "Certains texte de texte préfixe, 0,00" se convertiront en "un texte préfixe 1 234.56" qui ne peut pas être analysé.) xaml: p> xxx pré>

notez une copie cibleNullvalue si la propriété source peut être null. p>

C #: P>

/// <summary>
/// Allow a binding where the StringFormat is also bound to a property (and can vary).
/// </summary>
public class ToStringFormatConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 1)
            return System.Convert.ChangeType(values[0], targetType, culture);
        if (values.Length >= 2 && values[0] is IFormattable)
            return (values[0] as IFormattable).ToString((string)values[1], culture);
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        var targetType = targetTypes[0];
        var nullableUnderlyingType = Nullable.GetUnderlyingType(targetType);
        if (nullableUnderlyingType != null) {
            if (value == null)
                return new[] { (object)null };
            targetType = nullableUnderlyingType;
        }
        try {
            object parsedValue = ToStringFormatConverter.TryParse(value, targetType, culture);
            return parsedValue != DependencyProperty.UnsetValue
                ? new[] { parsedValue }
                : new[] { System.Convert.ChangeType(value, targetType, culture) };
        } catch {
            return null;
        }
    }

    // Some types have Parse methods that are more successful than their type converters at converting strings
    private static object TryParse(object value, Type targetType, CultureInfo culture)
    {
        object result = DependencyProperty.UnsetValue;
        string stringValue = value as string;

        if (stringValue != null) {
            try {
                MethodInfo mi;
                if (culture != null
                    && (mi = targetType.GetMethod("Parse",
                        BindingFlags.Public | BindingFlags.Static, null,
                        new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider) }, null))
                    != null) {
                    result = mi.Invoke(null, new object[] { stringValue, NumberStyles.Any, culture });
                }
                else if (culture != null
                    && (mi = targetType.GetMethod("Parse",
                        BindingFlags.Public | BindingFlags.Static, null,
                        new[] { typeof(string), typeof(IFormatProvider) }, null))
                    != null) {
                    result = mi.Invoke(null, new object[] { stringValue, culture });
                }
                else if ((mi = targetType.GetMethod("Parse",
                        BindingFlags.Public | BindingFlags.Static, null,
                        new[] { typeof(string) }, null))
                    != null) {
                    result = mi.Invoke(null, new object[] { stringValue });
                }
            } catch (TargetInvocationException) {
            }
        }

        return result;
    }
}


1 commentaires

J'aime que cela utilise hors de la boîte WPF et que la méthode d'analyse pourrait facilement être adaptée à votre situation spécifique.



2
votes

Ceci est une solution de Andrew Olson qui utilise des propriétés attachées et peut donc être utilisée dans différentes situations.

Utilisé comme ceci: p> xxx pré>

L'aide requise: ( GIST SOURCE ) P>

public static class StringFormatHelper
{
    #region Value

    public static DependencyProperty ValueProperty = DependencyProperty.RegisterAttached(
        "Value", typeof(object), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnValueChanged));

    private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        RefreshFormattedValue(obj);
    }

    public static object GetValue(DependencyObject obj)
    {
        return obj.GetValue(ValueProperty);
    }

    public static void SetValue(DependencyObject obj, object newValue)
    {
        obj.SetValue(ValueProperty, newValue);
    }

    #endregion

    #region Format

    public static DependencyProperty FormatProperty = DependencyProperty.RegisterAttached(
        "Format", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null, OnFormatChanged));

    private static void OnFormatChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        RefreshFormattedValue(obj);
    }

    public static string GetFormat(DependencyObject obj)
    {
        return (string)obj.GetValue(FormatProperty);
    }

    public static void SetFormat(DependencyObject obj, string newFormat)
    {
        obj.SetValue(FormatProperty, newFormat);
    }

    #endregion

    #region FormattedValue

    public static DependencyProperty FormattedValueProperty = DependencyProperty.RegisterAttached(
        "FormattedValue", typeof(string), typeof(StringFormatHelper), new System.Windows.PropertyMetadata(null));

    public static string GetFormattedValue(DependencyObject obj)
    {
        return (string)obj.GetValue(FormattedValueProperty);
    }

    public static void SetFormattedValue(DependencyObject obj, string newFormattedValue)
    {
        obj.SetValue(FormattedValueProperty, newFormattedValue);
    }

    #endregion

    private static void RefreshFormattedValue(DependencyObject obj)
    {
        var value = GetValue(obj);
        var format = GetFormat(obj);

        if (format != null)
        {
            if (!format.StartsWith("{0:"))
            {
                format = String.Format("{{0:{0}}}", format);
            }

            SetFormattedValue(obj, String.Format(format, value));
        }
        else
        {
            SetFormattedValue(obj, value == null ? String.Empty : value.ToString());
        }
    }
}


1 commentaires

J'ai besoin d'ajouter "chemin" et "RelateSource" pour le faire fonctionner. Stackoverflow.com/a/5832247/417939