views:

54

answers:

5

I'm getting started with WPF and trying to get my head around connecting data to the UI. I've managed to connect to a class without any issues, but what I really want to do is connect to a property of the main window.

Here's the XAML:

<Window x:Class="test3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:test3"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <CollectionViewSource
        Source="{Binding Source={x:Static Application.Current}, Path=Platforms}"
        x:Key="platforms"/>
    <DataTemplate DataType="{x:Type custom:Platform}">
        <StackPanel>
            <CheckBox IsChecked="{Binding Path=Selected}"/>
            <TextBlock Text="{Binding Path=Name}"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<Grid>
    <ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
</Grid>

Here's the code for the main window:

public partial class MainWindow : Window
{
    ObservableCollection<Platform> m_platforms;

    public MainWindow()
    {
        m_platforms = new ObservableCollection<Platform>();

        m_platforms.Add(new Platform("PC"));

        InitializeComponent();
    }

    public ObservableCollection<Platform> Platforms
    {
        get { return m_platforms; }
        set { m_platforms = value; }
    }
}

Here's the Platform class:

public class Platform
{
    private string m_name;
    private bool m_selected;

    public Platform(string name)
    {
        m_name = name;
        m_selected = false;
    }

    public string Name
    {
        get { return m_name; }
        set { m_name = value; }
    }

    public bool Selected
    {
        get { return m_selected; }
        set { m_selected = value; }
    }
}

This all compiles and runs fine but the list box displays with nothing in it. If I put a breakpoint on the get method of Platforms, it doesn't get called. I don't understand as Platforms is what the XAML should be connecting to!

+2  A: 

Your code looks ok apart from the fact that the Binding on Source on CollectionViewSource is not correct. You probably meant this:

 <CollectionViewSource
    Source="{Binding Source={x:Static Application.Current}, Path=MainWindow.Platforms}"
    x:Key="platforms"/>

Without this change the Binding actually looked for property Platforms on Application instance.

wpfwannabe
Yep, that got it working. I changed the StackPanel orientation as well, otherwise the check box appears above the name.
imekon
+1  A: 

I would suggest you add the platforms not to the MainWindow but rather set it as the MainWindow's DataContext (wrapped inside a ViewModel).

That way you can very easily bind against it (the binding code would look like ItemsSource={Binding Path=Platforms}).

This is part of WPFs design, that every form should have a explicit DataContext it binds to.

Tigraine
I'll have to investigate DataContext to see how they work, thanks
imekon
+1  A: 

A somewhat more appropriate solution is to give your window a name. A nice convention is _this.

<Window x:Name="_this" x:Class="test3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:test3"
    Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <CollectionViewSource
            Source="{Binding ElementName=_this, Path=Platforms}"
            x:Key="platforms"/>
        <DataTemplate DataType="{x:Type custom:Platform}">
            <StackPanel>
                <CheckBox IsChecked="{Binding Path=Selected}"/>
                <TextBlock Text="{Binding Path=Name}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding Source={StaticResource platforms}}"/>
    </Grid>
nedruod
That's much more elegant!
imekon
A: 

Here's my updated code, the XAML:

<Window x:Name="_this"
    x:Class="test3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:test3"
    Title="MainWindow" Height="190" Width="177">
    <Window.Resources>
        <CollectionViewSource
            Source="{Binding ElementName=_this, Path=Platforms}"
            x:Key="platforms"/>
        <DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
            <StackPanel Orientation="Horizontal">
                <CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
                <TextBlock Margin="1" Text="{Binding Path=Name}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="23" />
            <RowDefinition Height="23" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0"
            ItemsSource="{Binding Source={StaticResource platforms}}" 
            ItemTemplate="{StaticResource platformTemplate}"/>
        <Button Click="OnBuild" Grid.Row="1">Build...</Button>
        <Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
    </Grid>
</Window>

The code behind the XAML:

public partial class MainWindow : Window
{
    ObservableCollection<Platform> m_platforms;

    public MainWindow()
    {
        m_platforms = new ObservableCollection<Platform>();

        m_platforms.Add(new Platform("PC"));
        m_platforms.Add(new Platform("PS3"));
        m_platforms.Add(new Platform("Xbox 360"));

        InitializeComponent();
    }

    public ObservableCollection<Platform> Platforms
    {
        get { return m_platforms; }
        set { m_platforms = value; }
    }

    private void OnBuild(object sender, RoutedEventArgs e)
    {
        string text = "";

        foreach (Platform platform in m_platforms)
        {
            if (platform.Selected)
            {
                text += platform.Name + " ";
            }
        }

        if (text == "")
        {
            text = "none";
        }

        MessageBox.Show(text, "WPF TEST");
    }

    private void OnTogglePC(object sender, RoutedEventArgs e)
    {
        m_platforms[0].Selected = !m_platforms[0].Selected;
    }
}

...and finally the Platform code, enhanced to finish off the two way interaction:

public class Platform : INotifyPropertyChanged
{
    private string m_name;
    private bool m_selected;

    public Platform(string name)
    {
        m_name = name;
        m_selected = false;
    }

    public string Name
    {
        get { return m_name; }
        set
        {
            m_name = value;

            OnPropertyChanged("Name");
        }
    }

    public bool Selected
    {
        get { return m_selected; }
        set
        {
            m_selected = value;

            OnPropertyChanged("Selected");
        }
    }

    private void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
imekon
A: 

Using DataContext, it gets even easier!

<Window x:Class="test5.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:test5"
    Title="MainWindow" Height="190" Width="177">
    <Window.Resources>
        <CollectionViewSource
            Source="{Binding Path=.}"
            x:Key="platforms"/>
        <DataTemplate x:Key="platformTemplate" DataType="{x:Type custom:Platform}">
            <StackPanel Orientation="Horizontal">
                <CheckBox Margin="1" IsChecked="{Binding Path=Selected}"/>
                <TextBlock Margin="1" Text="{Binding Path=Name}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="23" />
            <RowDefinition Height="23" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0"
            ItemsSource="{Binding Source={StaticResource platforms}}" 
            ItemTemplate="{StaticResource platformTemplate}"/>
        <Button Click="OnBuild" Grid.Row="1">Build...</Button>
        <Button Click="OnTogglePC" Grid.Row="2">Toggle PC</Button>
    </Grid>
</Window>

Here's the code behind this:

private ObservableCollection<Platform> m_platforms;

public MainWindow()
{
    InitializeComponent();

    m_platforms = new ObservableCollection<Platform>();

    m_platforms.Add(new Platform("PC"));
    m_platforms.Add(new Platform("PS3"));
    m_platforms.Add(new Platform("Xbox 360"));

    DataContext = m_platforms;
}

public void OnBuild(object sender, RoutedEventArgs e)
{
    string text = "";

    foreach (Platform platform in m_platforms)
    {
        if (platform.Selected)
        {
            text += platform.Name + " ";
        }
    }

    if (text == "")
    {
        text = "none";
    }

    MessageBox.Show(text, "WPF TEST");
}

public void OnTogglePC(object sender, RoutedEventArgs e)
{
    m_platforms[0].Selected = !m_platforms[0].Selected;
}

Note that I've dropped the need to declare Platforms as a property of the main window, instead I assign it to the DataContext, and the XAML source becomes, simply, "."

imekon
That's odd, this answer appears before the following, yet I added it later...
imekon
They are ordered by ranking
nedruod
By the way, pretty sure you don't need Path=., simply {Binding} is equivalent.
nedruod