tags:

views:

57

answers:

2

Hello,

I am trying to bind a data class to a ListView control in WPF, but can't seem to get it working. I can bind it at run-time and get it working using the following:

this.DataContext = DataSet;

But, if I try the following in the WPF/XAML is doesn't work which looks like:

DataContext="DiscoveredItemContainer"

I have tried various permutations, but nothing I try works. I could just use the run-time version, as it works, but it is bugging me that I can't make the XAML bind the control correctly. Perhaps this can't work since the dataset is dynamic in nature, but that is just a thought.

I am not sure if the code will help answer the question or not, but I will post what is relevant, I hope any how. I left out the using declarations.

This is the XAML for the form

<Window x:Class="Viking.Test.DataBindTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Items="clr-namespace:Viking.Test.Discovery"
xmlns:data="clr-namespace:Viking.Test"
Title="Data Binding Test" Height="300" Width="500"
DataContext="DiscoveredItemContainer"> 
  <DockPanel Name="_DockPanel" Height="Auto" Width="Auto">
    <Menu Name="_Menu" DockPanel.Dock="Top" Height="22" Width="Auto" VerticalContentAlignment="Center" VerticalAlignment="Top">
      <MenuItem Name="_File" Header="File">
        <MenuItem Name="_AddOne" Header="Add One" Click="AddOne_Click" />
      </MenuItem>
    </Menu>
    <ListView Name="listView1" Height="Auto" Width="Auto" ItemsSource="{Binding Path=DiscoveredItems}">
      <ListView.View>
        <GridView AllowsColumnReorder="True">
          <GridViewColumn Header="Field1" DisplayMemberBinding="{Binding Field1}" Width="100" />
        </GridView>
      </ListView.View>
    </ListView>
  </DockPanel>
</Window>

Here is the partial class that goes with the XAML

namespace Viking.Test {
public partial class DataBindTest : Window
{
  private DiscoveredItemList DiscoveredItemContainer;

  public DataBindTest() {
    InitializeComponent();
    DiscoveredItemContainer = new DiscoveredItemList();
  // Uncomment the following line to get the databinding to work
  //  this.DataContext = DiscoveredItemContainer;
  }

  private void AddOne_Click(object sender, RoutedEventArgs e) {
    DiscoveredItemContainer.AddRandomItem();
  }
}  // End of Class 
}  // End of Namespace 

The following is the class that contains the dataset
namespace Viking.Test.Discovery {
public class DiscoveredItem {

  public DiscoveredItem() {}   

  public string Field1        { get; set; }

}  // End of Class 
}  // End of Namespace 

Finally, this is the class that will expose the variable that is ObservableCollection in order to bind the data to the class

namespace Viking.Test.Discovery {
class DiscoveredItemList {
  public ObservableCollection<DiscoveredItem> DiscoveredItems { get; set; }
  private Random RandomGen;

  public DiscoveredItemList() {
    DiscoveredItems = new ObservableCollection<DiscoveredItem>();
    RandomGen = new Random();
  }

  public void AddRandomItem() {
    DiscoveredItem di = new DiscoveredItem();;
    di.Field1 = RandomGen.Next(1,10).ToString();
    DiscoveredItems.Add(di);
  }
}  // End of Class 
}  // End of Namespace 

I have seen a great number of articles of binding a control to another control that is on the form, or binding during run-time (which is how I can get this to work), or bind to static resources. Any insight as to why I can't get this approach to work is appreciated. Thanks.

Mark

+1  A: 

you should replace

DataContext="DiscoveredItemContainer"

by

DataContext="{Binding DiscoveredItemContainer}"
la mouette
That wouldn't work, because (a) DiscoveredItemContainer is private, (b), the DataContext is not the current object
Thomas Levesque
+2  A: 

By setting your DataContext in XAML, you've decided to instantiate a new DiscoveredItemList instead of binding to the one in your codebehind. Therefore your codebehind won't have a direct reference to the DiscoveredItemList, but you can always search for it by name.

The first thing you need to do is remove:

DataContext="DiscoveredItemContainer"

and replace with:

<Window.Resources>
   <Items:DiscoveredItemList x:Key="Context"></Items:DiscoveredItemList>
</Window.Resources>

This instantiates a new object that your xaml can now bind to. Your list view should bind to it this way:

<ListView Name="listView1" Height="Auto" Width="Auto" DataContext="{StaticResource Context}" ItemsSource="{Binding DiscoveredItems}">
...
</ListView>

You must also gut out all the references to DiscoveredItemContainer in your code behind, and when you want to access the Items:DiscoveredItemList, such as to add a new random item, do this:

private void AddOne_Click(object sender, RoutedEventArgs e)
{
   var list = this.Resources["Context"] as DiscoveredItemList;
   list.AddRandomItem();
}

You could perhaps store a local reference to the item if you want to.

Here's the complete code for your copy/pasting pleasure. Note that I've tested this and "it works for me" :P

namespace Viking.Test
{
    public partial class DataBindTest : Window
    {
        public DataBindTest()
        {
            InitializeComponent();
        }

        private void AddOne_Click(object sender, RoutedEventArgs e)
        {
            var list = this.Resources["Context"] as DiscoveredItemList;
            list.AddRandomItem();
        }
    }  // End of Class 
}  // End of Namespace 

//The following is the class that contains the dataset
namespace Viking.Test.Discovery
{
    public class DiscoveredItem
    {
        public DiscoveredItem() { }

        public string Field1 { get; set; }

    }  // End of Class 

     public class DiscoveredItemList
    {
        public ObservableCollection<DiscoveredItem> DiscoveredItems { get; set; }
        private Random RandomGen;

        public DiscoveredItemList()
        {
            DiscoveredItems = new ObservableCollection<DiscoveredItem>();
            RandomGen = new Random();
        }


        public void AddRandomItem()
        {
            DiscoveredItem di = new DiscoveredItem(); ;
            di.Field1 = RandomGen.Next(1, 10).ToString();
            DiscoveredItems.Add(di);
        }
    }  // End of Class 
}

XAML:

<Window x:Class="Viking.Test.DataBindTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Items="clr-namespace:Viking.Test.Discovery" xmlns:data="clr-namespace:Viking.Test" Title="Data Binding Test" Height="300" Width="500" >
    <Window.Resources>
        <Items:DiscoveredItemList x:Key="Context"></Items:DiscoveredItemList>
    </Window.Resources>
    <DockPanel Name="_DockPanel" Height="Auto" Width="Auto" >
        <Menu Name="_Menu" DockPanel.Dock="Top" Height="22" Width="Auto" VerticalContentAlignment="Center" VerticalAlignment="Top">
            <MenuItem Name="_File" Header="File">
                <MenuItem Name="_AddOne" Header="Add One" Click="AddOne_Click" />
            </MenuItem>
        </Menu>
        <ListView Name="listView1" Height="Auto" Width="Auto" DataContext="{StaticResource Context}" ItemsSource="{Binding DiscoveredItems}">
            <ListView.View>
                <GridView AllowsColumnReorder="True">
                    <GridViewColumn Header="Field1"   DisplayMemberBinding="{Binding Field1}" Width="100" />
                </GridView>
            </ListView.View>
        </ListView>
    </DockPanel>
</Window>

Reference: http://stackoverflow.com/questions/606803/silverlight-setting-datacontext-in-xaml-rather-than-in-constructor

bufferz
Brilliant, worked great. I just needed to change those few lines as you stated, compiled, and ran the project then bam! Worked great!! Lastly, is either method preferable over the other one??
lordhog
It's really a matter of preference. But in general, the cleaner the separation between the UI and "business" logic, the better off you'll be. In that sense, your View (the XAML code) should not know nor care what it's data context is, as long as DiscoveredItems exists in there somewhere. So I would suggest you undo everything I just posted, set the DiscoveredItemContainer as a public property, and then set this.DataContext = DiscoveredItemContainer . It will be less hassle in the long run.
bufferz
You would also benefit from having a middle layer between your UI and the DiscoveredItemsContainer class. That would be a ViewModel. If you haven't already explored the world of MVVM, please do take some some to watch this excellent video: http://blog.lab49.com/archives/2650Your WPF and binding skills will take off like a rocket once you get the hang of MVVM, and you'll be writing cleaner code than ever.
bufferz
Thanks, Bufferz. This is my first project using WPF (have always used WinForms in the past). I will take a look at the blog and MVVM and see if I can incorporate that into my code.
lordhog