开发者

Data Binding Scopes - In Need of clarification

I've been doing some work in WPF, and I for the most part have my bindings working, but I need a scope clarification here. I've run into some seemingly simple operations that require silly binding workarounds and I believe a lot of it has to do with scope.

Example #1 - Out of visual tree, binding to parent.

<ComboBox x:Name="Combo1" ItemsSource="{Binding SomeListOfStrings}">
  <ComboBox.ContextMenu>
    <ContextMenu>
      <MenuItem Header="{Binding ElementName=Combo1, Path=SelectedItem}" />
      <MenuItem Header="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=SelectedItem}" />
    </ContextMenu>
  </ComboBox.ContextMenu>
</ComboBox>

In this example, I'm trying to bind a child-element's property to a parent's property. Since this item isn't in the visual tree under the element, but instead as just a property, I cannot locate the parent using FindAncestor. In my experience I've had no luck binding with ElementName in this case either (Tried both Name="" and x:Name="").

What is the scope here? How does the MenuItem relate to the ComboBox? Because I know it inherits the DataContext of it's parent here, why is it unreachable with FindAncestor / ElementName?

Example #2 - Resources + StaticResource/DynamicResource

<UserControl x:Name="MainControl" ... />
  <UserControl.Resources>
     <Style TargetType="ComboBox">
       <Setter Property="ContextMenu">
         <Setter.Value>
           <ContextMenu ItemsSource="{Binding ViewModelsMenuItems}" />
         </Setter.Value>
       </Setter>
     </Style>
     <Style TargetType="ComboBox" x:Key="Example2_Style2">
       <Setter Property="ContextMenu">
         <Sette开发者_如何转开发r.Value>
           <ContextMenu ItemsSource="{Binding ElementName=MainControl, Path=DataContext.ViewModelMenuItems}" />
         </Setter.Value>
       </Setter>
     </Style>
  </UserControl.Resources>
  <StackPanel>
    <ComboBox />
    <ComboBox />
    <ComboBox Style="{StaticResource Example2_Style2" />
  </StackPanel>
</UserControl>

In this example, I'm trying to set the context menu for all ComboBox's in my user control (or specific ones if I used a named style). Since the ContextMenu is defined outside of scope, and "set" into scope, I've experienced issues before with the DataContext being inherited, Or being able to use ElementName (Because the item is out of scope?).

Bonus Question

Since I've had terrible luck alltogether with ElementName can someone please tell me which to use, because I see both ALL OVER the internet/books. Name="Whatever" or x:Name="Whatever"

Update (As per request)

The type of binding failures I am getting are:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ComboBox', AncestorLevel='1''. BindingExpression:Path=SelectedItem; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Header' (type 'object')


There is something called inheritance context on which there is not all too much information to be found except this blog post (archive).

In that article the ContextMenu is given as an example where no linking occurs.

I for one would solve this issue using the PlacementTarget property:

<ContextMenu>
  <MenuItem Header="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItem}" />
</ContextMenu>

Original article has dissapeared; here are the main points from an archive:

Nick on Silverlight and WPF

What’s an inheritance context?

Nick Kramer [MSFT] August 17, 2006

But before I tell you about inheritance contexts, I have to explain the problem it solves. Once upon a time, property inheritance looked only at the logical tree and the visual tree -- so if an element didn't have a logical or visual parent, its properties didn't inherit values from the parent it didn't have. Which makes sense if you think about the world in terms of code. But if you look at the world through xaml glasses, there's a lot of places where one element looks like it has a parent when it really doesn't. Consider the following:

<Button>
  <Button.Background>
    <SolidColorBrush>green</SolidColorBrush>
  </Button.Background>
</Button>

Quick, what's the parent of SolidColorBrush? If you said Button, you would be wrong -- SolidColorBrush is not part of the visual tree (it's not a Visual). Nor is SolidColorBrush part of the logical tree, because if you call Button.Content the answer is not the SolidColorBrush. So SolidColorBrush in this example has no parent, so it didn't inherit property values from anyone.

At first this may seem academic -- who cares if SolidColorBrush inherits? Actually, there's a couple reasons it matters, the DataContext property and the Loaded event. DataContext is an inherited property that use the default data source for your {Binding} statements. When you write:

    <SolidColorBrush Color="{Binding}"/>

Since you didn't specify a data source (and who does), it uses the DataContext property. And if that inherits the way you expect, everything is happy. But it's easy to write:

<Button DataContext="whatever">
  <Button.Background>
    <SolidColorBrush Color="{Binding}"/>
  </Button.Background>
</Button>

And be confused that your SolidColorBrush didn't inherit the DataContext. Similarly, the Loaded event was originally tied to the logical tree, so if you put your MediaElement inside a VisualBrush, there would be a gap in the visual tree and your media would never get a Loaded event and never start play the video.

And that's why we invented inheritance context. You can think of it as logical tree 2.0 -- inheritance context is an extra pointer, which the property engine uses when there's no logical parent or visual parent to get values from. Inheritance context don't solve every problem in this space, but they solve a lot of them, and in the future we'll add more inheritance context pointers and solve more problems.

There's a number of places we establish inheritance context pointers, I won't try to list all of them but here are some of the more interesting ones:

Freezable inside a FrameworkElement -- our SolidColorBrush/Button sample above FrameworkElement inside a VisualBrush Triggers and setters

Resource dictionaries present another interesting case. Suppose you use DynamicResource inside a resource dictionary:

Does that dynamic resource get evaluated where the SolidColorBrush was defined? Or where the brush gets used? If the latter, what happens if you use the SolidColorBrush in two different places where the DynamicResource will give two different answers? It may sound contrived:

<Window.Resources>

  `<Color x:Key="color">red</Color>`

  `<SolidColorBrush x:Key="brush" Color="{DynamicResource color}" />  `

</Window.Resources>

<Button>
  <Button.Background>
    <StaticResource ResourceKey="brush"/>
  </Button.Background>
</Button>


<Button>
  <Button.Resources>
    <Color x:Key="color">blue</Color>
  </Button.Resources>
  <Button.Background>
    <StaticResource ResourceKey="brush"/>
  </Button.Background>
</Button>

But it actually happens in real code. We chose the first solution, the inheritance context for SolidColorBrush points to the resource dictionary, and not its point of use.

Inheritance context has been wonderfully useful, but we haven't put inheritance context links everywhere that's theoretically possible, mostly because of time in the day (adding inheritance context links are very difficult to implement performantly and without causing undesired behavior changes). Probably the simplest example of where we don't have inheritance context links is across random property elements:

<Button>
  <Button.ContextMenu>
    <ContextMenu/>
  </Button.ContextMenu>
</Button>

ContextMenu is neither a visual nor logical child of Button, nor is it one of the inheritance context cases listed above (ContextMenu is not a Freezable). But, with inheritance context concept at our disposal, we hope to solve that in future versions of WPF.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜