Ich habe einige angefügte Eigenschaften erstellt, um Indirektion einer Bindung zu ermöglichen (mit dem ich binde auf einen Wert, dessen Name von der angefügten Eigenschaft angegeben wird, anstatt als Literal in XAML angegeben).Binding Errors auf teilweise konstruierte Objekte
Einige der APs sind optional (zum Beispiel eines, die die DataContext
überschreibt, die sonst in Kraft sein würden), und dies bedeutet, dass ich die Bindung zu schaffen versucht bin, wenn nicht alle APs eingestellt worden ist (weil in den PropertyChangedCallback
I weiß nicht, ob die anderen eingestellt werden).
Das Ergebnis ist, dass die Bindung mehrmals, manchmal erfolglos erstellt werden kann, und dies führt zu Bindungsfehlern, die "unansehnlich" sind, für ein besseres Wort.
Gibt es eine Möglichkeit, Bindungsfehler zu unterdrücken, bis alle APs eines Elements zugewiesen wurden, oder innerhalb von PropertyChangedCallback
herauszufinden, ob weitere APs der enthaltenen Klasse für dieses Element gesetzt werden?
bearbeiten
Ich habe Code gefragt. Ich habe gehofft, dies zu tun, ohne, aber hier ist die Klasse, über die ich frage, (weil es die Frage ziemlich lang gemacht hat!):
public static class BindingIndirector
{
public static string GetBindingSource(DependencyObject dob)
{
return (string)dob.GetValue(BindingSourceProperty);
}
public static void SetBindingSource(DependencyObject dob, string value)
{
dob.SetValue(BindingSourceProperty, value);
}
/// <summary>
/// The "source" to be set on the binding.
/// Must be specified.
/// </summary>
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.RegisterAttached(
"BindingSource",
typeof(String),
typeof(BindingIndirector),
new PropertyMetadata(null, BindingChanged));
public static object GetBindingSourceContext(DependencyObject dob)
{
return dob.GetValue(BindingSourceContextProperty);
}
public static void SetBindingSourceContext(DependencyObject dob, object value)
{
dob.SetValue(BindingSourceContextProperty, value);
}
/// <summary>
/// A DataContext type property. This overrides the inherited DataContext that would otherwise be
/// used for the binding.
/// Optional.
/// </summary>
public static readonly DependencyProperty BindingSourceContextProperty =
DependencyProperty.RegisterAttached(
"BindingSourceContext",
typeof(object),
typeof(BindingIndirector),
new PropertyMetadata(null, BindingChanged));
public static string GetBindingTarget(DependencyObject dob)
{
return (string)dob.GetValue(BindingTargetProperty);
}
public static void SetBindingTarget(DependencyObject dob, string value)
{
dob.SetValue(BindingTargetProperty, value);
}
/// <summary>
/// The binding target property.
/// Optional (defaults to "Content" if not specified
/// </summary>
public static readonly DependencyProperty BindingTargetProperty =
DependencyProperty.RegisterAttached(
"BindingTarget",
typeof(String),
typeof(BindingIndirector),
new PropertyMetadata("Content", BindingChanged));
private static void BindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(e.Property == BindingSourceContextProperty || e.NewValue is string))
throw new ArgumentException("Property can only be set to string values", e.Property.ToString());
// Check rules for attempting to set the binding are met
string source = GetBindingSource(d) as string;
string target = GetBindingTarget(d) as string;
object context = GetBindingSourceContext(d);
if (source == null) // Source needs to be set - don't interfere with binding if it isn't
return;
// Clear any existing binding
var originalName = e.Property ==
BindingSourceProperty ?
target :
e.OldValue as string;
if (originalName != null)
{
var existingDescriptor =
DependencyPropertyDescriptor.FromName(
originalName,
d.GetType(),
d.GetType());
if (existingDescriptor != null)
d.ClearValue(existingDescriptor.DependencyProperty);
}
// Create and assign new binding
var targetDescriptor =
DependencyPropertyDescriptor.FromName(
target,
d.GetType(),
d.GetType());
if (targetDescriptor != null) // don't interfere with binding if target invalid
{
Binding newBinding = new Binding(source) { Mode = BindingMode.TwoWay };
if (context != null) // Will fall back to DataContext of element in this case
newBinding.Source = context;
BindingOperations.SetBinding(d, targetDescriptor.DependencyProperty, newBinding);
}
}
}
Diese statische Klasse erstellt 3 hinzugefügte Eigenschaften und auch eine einzelne Methode enthält " BindingChanged() "das ist die propertyChangedCallback
für alle drei APs. Wenn genügend Informationen gegeben wurden, um eine Bindung zu erstellen, wird dies getan, indem jede vorherige Bindung verworfen wird, die die APs zuerst verwendet haben.
Was es nicht macht (was eine Lösung sein könnte) ist, herauszufinden, ob die Bindung zuerst erfolgreich sein würde oder irgendwelche Fehler, die von der Bindungs-Engine erzeugt werden (können Sie das tun?). Es könnte eine Herausforderung sein, Binding-Fehler nicht zu unterdrücken, die angezeigt werden sollten (weil der Endbenutzer beispielsweise duff-Informationen angegeben hat). Hier
ist ein Beispiel für einen Anwendungsfall:
<UserControl x:Class="UtilityControls.ListEditor"
...>
<Grid x:Name="ControlContainer">
<Grid.DataContext>
<local:LeViewModel x:Name="vm" />
</Grid.DataContext>
<ListBox
x:Name="EditingArea"
ItemsSource="{Binding ColumnCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:ListEditor}}}"
>
<ListBox.Resources>
<DataTemplate x:Key="TextTemplate">
<StackPanel>
<TextBlock Text="{Binding DisplayName}" />
<TextBox
local:BindingIndirector.BindingSourceContext="{Binding DataContext.CurrentEditing, ElementName=ControlContainer}"
local:BindingIndirector.BindingSource="{Binding PathName}"
local:BindingIndirector.BindingTarget="Text"
/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="PickListTemplate" .. />
<DataTemplate x:Key="BooleanTemplate" ... />
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<local:DataTypeSelector
TextTemplate="{StaticResource TextTemplate}"
PickListTemplate="{StaticResource PickListTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"
/>
</ListBox.ItemTemplateSelector>
</ListBox>
</Grid>
</UserControl>
„CurrentEditing“ ist die Ansichtsmodell-Objekt der verschiedenen ListBox
Artikel bearbeiten (jeweils ListBox
Element aus ColumnCollection
einen Editor für eine andere Eigenschaft des Objekts erzeugt).
Hoffentlich wird der Zweck der APs (hier in „TextTemplate“ verwendet) ist selbsterklärend (sie schaffen für die Text
Eigenschaft des TextBox
eine Bindung), aber beachten Sie, dass, obwohl alle drei hier notwendig sind, möchte ich auf die mindestens BindingSourceContext
optional zu sein ... und das schafft das Problem: BindingChanged()
weiß nicht, wie viele der APs eingestellt werden, so dass es nicht weiß, wann die Bindung zu erstellen ist. Daher muss jedes Mal, wenn eine Eigenschaft geändert wird, eine entsprechende Änderung vorgenommen werden. Wenn es noch mehr gibt, werden Binding-Fehler erzeugt.
was meinst du mit "unansehnlich", sind diese verbindlichen Fehler nur in VSs Ausgabefenster und sie stören Sie? Kannst du etwas Code darüber zeigen, was du gerade machst und was genau die Fehler sind? –
Ja, ich meine das VS-Ausgabefenster.Meiner Erfahrung nach haben Fehler normalerweise zur Folge, dass eine Bindung falsch spezifiziert wurde und ich die "UserControl", die ich erschaffe, lieber nicht selbst erzeugen würde (was sie gerade macht) und die Ausgabe mit falschen Positives verunreinigt. Die Fehler sind "System.Windows.Data Error: 40: BindingExpression path error" und treten in diesem Fall auf, weil die Binding-Engine versucht, einen Pfad auf dem geerbten 'DataContext' zu finden (falsch - es ist nicht da und ein anderer AP legt 'Binding.Source' fest, um dies zu korrigieren. –
ok, kannst du uns also Code zeigen? –