开发者

Why doesn't this WPF binding work?

I'm having trouble getting a binding to work. I have created a completely fruitless example to demonstrate my problem. It goes like this...

I have a collection of widgets that I want to be able to define in XAML. This collection will be associated with a UserControl. In the XAML I want to bind the 'Name' property of the widgets to bits of data (strings in this example).

So, for code I have a Widget class that derives from DependencyObject

public class Widget : DependencyObject
{
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Name.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty NameProperty =
        DependencyProperty.Register("Name", typeof(string), typeof(Widget), new UIPropertyMetadata("--widget--"));

}

Notice the default value for this property is "--widget--". I also have a WidgetCollection class...

public class WidgetCollection : Collection<Widget>
{
    public WidgetCollection()
    {
    }
}

The WidgetControl is the parent of a WidgetCollection that it exposes as a property named 'Widgets'

[ContentPropertyAttribute("Widgets")]
public partial class WidgetControl : UserControl
{

    public WidgetCollection Widgets
    {
        get { return (WidgetCollection)GetValue(WidgetsProperty); }
        set { SetValue(WidgetsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Widgets.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty WidgetsProperty =
        DependencyProperty.Register("Widgets", typeof(WidgetCollection), typeof(WidgetControl), new UIPropertyMetadata(null));


    public WidgetControl()
    {
        Widgets = new WidgetCollection();
        InitializeComponent();
    }

    public IEnumerable<string> GetWidgetNames()
    {
        return Widgets.Select(w => w.Name);
    }

}

For visual feedback, the WidgetControl displays a red rectangle...

<UserControl x:Class="StackOverflow.BindingProblem.WidgetControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Rectangle Margin="0" Fill="Red"/>    
    </Grid>
</UserControl>

So now that I've got all the pieces for my demo, I'll put them together in the MainWindow...

    <Window x:Class="StackOverflow.BindingProblem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:StackOverflow.BindingProblem"
        Title="MainWindow" Height="220" Width="160"
        x:Name="x_win">
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <l:WidgetControl x:Name="x_WidgetControl" Grid.Row="0">
            <l:Widget Name="Sprocket"/>
            <l:Widget Name="{Binding Path=WidgetName1, ElementName=x_win}"/>
            <l:Widget Name="{Binding Path=WidgetName2, ElementName=x_win}"/>
            <l:Widget Name="Wrench"/>
        </l:WidgetControl>

        <StackPanel Orientation="Vertical" Grid.Row="1">
            <TextBlock Text="Sprocket"/>
            <TextBlock Text="{Binding Path=WidgetName1, ElementName=x_win}"/>
            <TextBlock Text="{Binding Path=WidgetName2, ElementName=x_win}"/>
            <TextBlock Text="Wrench"/>
        </StackPanel>

        <ListBox x:Name="x_ListBox" Grid.Row="2"/>
    </Grid>
</Window>

Here, I've dumped the results to the window so you can see what I'm talking about without having to use a debugger. I'm using a grid to create three rows. The top shows the WidgetControl (an exciting red rectangle!) to which I'm defining 4 widgets in the XAML. Note that two of the widgets have hard-coded names and the other two have names data-bound to string properties defined on the MainWindow class. The middle row shows four TextBlocks that have the exact same bindings. The bottom row shows a listbox. This listbox is populated with the names of the widgets in the WidgetCollection owned by the WidgetControl. This list is gather开发者_开发百科ed AFTER the window is loaded, and thus after the data binding is complete.

The MainWindow code behind looks like this ...

public partial class MainWindow : Window
{
    public MainWindow()
    {
        Loaded += new RoutedEventHandler(MainWindow_Loaded);
        InitializeComponent();
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        x_ListBox.ItemsSource = x_WidgetControl.GetWidgetNames();
    }

    public string WidgetName1
    {
        get { return "Crank"; }
    }

    public string WidgetName2
    {
        get { return "Wheel"; }
    }
}

When this demo runs, the window looks like this...

[rats! picture ommitted because I'm a new submitter] I'll mock it up with text...

-----------
|         |
|         |  <--  red rectangle (the WidgetControl)
|         |
-----------
Sprocket
Crank        <--  textblocks
Wheel
Wrench
-----------
Sprocket
--widget--   <--  listbox
--widget--
Wrench
-----------

Note how the bindings on the TextBlocks work fine, but the bindings on the Widgets don't. The bottom row shows the unsuccessful bindings as --widget-- instead of Crank and Wheel.

So why does this happen and how can I get the bindings to work?


I'm just shooting in the dark here, but I think that your DependencyProperty Name is properly bound to, but when it is changed from "--widget--" to "Crank" the change is not propagated to wherever Name is displayed. You need to set a callback on DependencyProperty.Register.

What does Widget do exactly?


Well, I found some answers. Both of these techniques seems to do the trick, and both are very informative as well. Being a new poster, I can only submit one hyperlink. So you can search for "Attaching Virtual Branches to the Logical Tree - by Josh Smith", and/or you can go to this link for my favorite...

Using Freezable - Dr WPF

Personally, I like deriving my Widget from Freezable because it requires less plumming AND isn't sensitive to the order of initialization.. Josh's solution will only work when the DataContext is set AFTER InitializeComponent() is called, like so...

public MainWindow()
{
    Loaded += new RoutedEventHandler(MainWindow_Loaded);
    InitializeComponent();
    DataContext = this;
}

Note: With my example, it was not enough to simply derive Widget from Freezable. I also had to derive WidgetCollection from FreezableCollection !!

Case closed.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜