2009-11-19 5 views
6

Comment passer la mise à jour de certaines sous-liaisons d'un MultiBinding? J'ai défini dans code-behind (j'ai eu quelques problèmes en le faisant en XAML et je ne pense pas que cela compte - après tout code-behind n'est pas moins expressif que XAML) un MultiBinding qui prend deux propriétés en lecture seule et une propriété normale pour produire une seule valeur. Dans le cas de ConvertBack, les propriétés en lecture seule ne sont pas modifiées (elles conservent leur valeur) et seule la propriété normale est modifiée.TwoWay MultiBinding avec des propriétés en lecture seule

Bien que la définition de la MultiBinding l'ensemble MultiBinding a été fixée à TwoWay toutefois sous-ensemble approprié des liaisons où (première à deux OneWay et le troisième deux TwoWay) particuliers.


Le problème se produit dans un mon propre contrôle. Cependant, pour des raisons de présentation, je l'ai simplifié à un plus petit contrôle. Le contrôle présenté dans cet exemple est un contrôle similaire à Slider permettant de sélectionner une valeur dans [0.0; 1,0] plage. La valeur sélectionnée est représentée par le pouce et exposée en tant que DependencyProperty.

Fondamentalement, le contrôle est construit par une colonne 1 x 3 Grid où le pouce est dans la colonne du milieu. Pour positionner correctement la colonne à gauche du pouce, il faut attribuer une largeur correspondant à la position sélectionnée. Cependant, cette largeur dépend également de la largeur réelle du contrôle entier et de la largeur réelle du pouce lui-même (car la position est donnée comme une valeur relative dans la plage [0.0; 1.0]). Lorsque le pouce est déplacé, la position doit être mise à jour de manière appropriée, mais la largeur du pouce et la largeur du contrôle ne changent évidemment pas. Le code fonctionne comme prévu mais lorsqu'il est exécuté dans l'EDI pendant le déplacement du pouce La fenêtre de sortie est encombrée avec des informations d'exceptions comme indiqué lorsque MultiBinding essaie de définir la valeur de ces deux propriétés en lecture seule. Je soupçonne que ce n'est pas dangereux, mais il est un peu ennuyeux et trompeur. Et cela signifie aussi que le code fait quelque chose d'autre que je voulais faire car je ne voulais pas définir ces propriétés (ceci est important dans le cas où elles ne seraient pas en lecture seule et que cela les modifierait en fait).

MultiBindingdocumentation dans Remarques La section mentionne que les sous-liaisons individuelles sont autorisées à remplacer la valeur du mode MultiBinding, mais cela ne semble pas fonctionner.

Peut-être que cela pourrait être résolu d'une manière ou d'une autre en exprimant la dépendance sur le contrôle et les largeurs de pouce (les propriétés en lecture seule) d'une manière ou d'une autre. Par exemple, l'inscription à leurs notifications séparément et l'application de mise à jour lors de leur modification. Cependant cela ne me semble pas naturel. D'autre part, MultiBinding car après tout la largeur de la colonne de gauche dépend de ces trois propriétés.


Voici l'exemple de code XAML.

<UserControl x:Class="WpfTest.ExampleUserControl" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
<Grid> 
    <Grid.RowDefinitions> 
    <RowDefinition /> 
    </Grid.RowDefinitions> 
    <Grid.ColumnDefinitions> 
    <ColumnDefinition x:Name="leftColumn" /> 
    <ColumnDefinition x:Name="thumbColumn" Width="Auto" /> 
    <ColumnDefinition /> 
    </Grid.ColumnDefinitions> 
    <!-- Rectangle used in the left column for better visualization. --> 
    <Rectangle Grid.Column="0"> 
    <Rectangle.Fill> 
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> 
    <GradientStop Color="Black" Offset="0" /> 
    <GradientStop Color="White" Offset="1" /> 
    </LinearGradientBrush> 
    </Rectangle.Fill> 
    </Rectangle> 
    <!-- Thumb representing the Position property. --> 
    <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" /> 
    <!-- Rectangle used in the right column for better visualization. --> 
    <Rectangle Grid.Column="2"> 
    <Rectangle.Fill> 
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> 
    <GradientStop Color="White" Offset="0" /> 
    <GradientStop Color="Black" Offset="1" /> 
    </LinearGradientBrush> 
    </Rectangle.Fill> 
    </Rectangle> 
</Grid> 
</UserControl> 

Et voici le code-behind correspondant

using System; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 

namespace WpfTest 
{ 
public partial class ExampleUserControl : UserControl 
{ 
    #region PositionConverter 

    private class PositionConverter : IMultiValueConverter 
    { 
    public PositionConverter(ExampleUserControl owner) 
    { 
    this.owner = owner; 
    } 

    #region IMultiValueConverter Members 

    public object Convert(
    object[] values, 
    Type targetType, 
    object parameter, 
    CultureInfo culture) 
    { 
    double thisActualWidth = (double)values[0]; 
    double thumbActualWidth = (double)values[1]; 
    double position = (double)values[2]; 

    double availableWidth = thisActualWidth - thumbActualWidth; 

    double leftColumnWidth = availableWidth * position; 

    return new GridLength(leftColumnWidth); 
    } 

    public object[] ConvertBack(
    object value, 
    Type[] targetTypes, 
    object parameter, 
    CultureInfo culture) 
    { 
    double thisActualWidth = owner.ActualWidth; 
    double thumbActualWidth = owner.thumbColumn.ActualWidth; 
    GridLength leftColumnWidth = (GridLength)value; 

    double availableWidth = thisActualWidth - thumbActualWidth; 

    double position; 
    if (availableWidth == 0.0) 
    position = 0.0; 
    else 
    position = leftColumnWidth.Value/availableWidth; 

    return new object[] { 
    thisActualWidth, thumbActualWidth, position 
    }; 
    } 

    #endregion 

    private readonly ExampleUserControl owner; 
    } 

    #endregion 

    public ExampleUserControl() 
    { 
    InitializeComponent(); 

    MultiBinding leftColumnWidthBinding = new MultiBinding() 
    { 
    Bindings = 
    { 
    new Binding() 
    { 
     Source = this, 
     Path = new PropertyPath("ActualWidth"), 
     Mode = BindingMode.OneWay 
    }, 
    new Binding() 
    { 
     Source = thumbColumn, 
     Path = new PropertyPath("ActualWidth"), 
     Mode = BindingMode.OneWay 
    }, 
    new Binding() 
    { 
     Source = this, 
     Path = new PropertyPath("Position"), 
     Mode = BindingMode.TwoWay 
    } 
    }, 
    Mode = BindingMode.TwoWay, 
    Converter = new PositionConverter(this) 
    }; 
    leftColumn.SetBinding(
    ColumnDefinition.WidthProperty, leftColumnWidthBinding); 
    } 

    public static readonly DependencyProperty PositionProperty = 
    DependencyProperty.Register(
    "Position", 
    typeof(double), 
    typeof(ExampleUserControl), 
    new FrameworkPropertyMetadata(0.5) 
    ); 

    public double Position 
    { 
    get 
    { 
    return (double)GetValue(PositionProperty); 
    } 
    set 
    { 
    SetValue(PositionProperty, value); 
    } 
    } 

} 
} 

Répondre

9

Enfin, j'ai trouvé la solution moi-même. En fait, il est dans le documentation - je ne sais pas comment j'ai raté cela, mais j'ai payé cher (en temps perdu) pour cela.

Selon la documentation ConvertBackBinding.DoNothing doit revenir sur les positions sur lesquelles aucune valeur doit être réglée (en particulier, il y avait OneWay la liaison est souhaitée). Une autre valeur spéciale est DependencyProperty.UnsetValue.

Ceci n'est pas une solution complète car maintenant l'implémentation IMultiValueConverter doit savoir où retourner une valeur spéciale. Cependant, je pense que la plupart des cas raisonnables sont couverts par cette solution.

+0

Merci; c'est ce que j'avais besoin de savoir! Y at-il un moyen facile d'obtenir les valeurs d'entrée de ces propriétés dans un ConvertBack? –

+1

Merci! Peut-être que cela vaut la peine de mentionner également les différentes liaisons «internes» pour lesquelles vous voulez que la valeur soit reconvertie en mode «Mode = TwoWay». Je n'ai pas trouvé cela dans la documentation. : / –

4

Il ressemble à MultiBinding ne fonctionne pas correctement. J'ai vu un comportement inattendu (quelque chose comme le vôtre) avant dans ma pratique. Vous pouvez également insérer des points d'arrêt ou des traces dans le convertisseur et vous pouvez trouver des choses amusantes sur les convertisseurs et quand ils sont appelés. Donc, si c'est possible, vous devriez éviter d'utiliser MultiBinding. Par exemple.vous pouvez ajouter une propriété spéciale dans votre modèle de vue qui va définir la valeur de votre propriété mutable dans son setter et retourner la valeur nécessaire en utilisant vos trois propriétés dans son getter. C'est quelque chose comme un MultiValueConverter dans une propriété =).

Espérons que ça aide.

+0

Si c'est vraiment un bug dans MultiBinding alors peut-être que nous devrions le signaler d'une manière ou d'une autre. Où faire ça? –

+0

Avez-vous essayé de faire la même chose avec .NET 4.0? Peut-être que c'est corrigé maintenant. (Je ne peux pas essayer maintenant à cause de quelques problèmes techniques avec mon VS 2010). – levanovd

Questions connexes