I'm trying to populate a ListBox with data from an object source using data binding in WPF.
The source is an ObjectDataProvider whose data is loaded in from an xml file. I read in the XML file, filling in the appropriate data structure, then set the ObjectInstance for the ObjectDataProvider to the data structure.
Here is the data structure:
public class Element
{
public decimal myValue;
public decimal df_myValue { get { return myValue; } set { this.myValue = value; } }
}
public class BasicSet
{
public Element[] elementSet;
public Element[] df_elementSet { get { return elementSet; } set { this.elementSet = value; } }
}
public class DataSet
{
public BasicSet[] basicSet;
public BasicSet[] df_basicSet { get { return basicSet; } set { this.basicSet = value; } }
}
Here is the XAML:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="TheData" />
<DataTemplate x:Key="ElementTemplate">
<StackPanel>
<TextBox Text="{Binding, Path=df_myValue}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ElementSetTemplate" ItemsSource="{Binding Path=df_elementSet}" ItemTemplate="{StaticResource ElementTemplate}">
</HierarchicalDataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource TheData}" ItemTemplate="{StaticResource ElementSetTemplate}">
</ListBox>
</Grid>
Here is the code-behind where the xml data is being loaded:
private DataSet m_dataSet;
private ObjectDataProvider mODP;
public void LoadXml(EditorContext context, XmlValidator.Context validator, XmlDocument doc)
{
mODP = FindResource("TheData") as ObjectDataProvider;
XmlSerializer xs = new XmlSerializer(typeof(DataSet));
XmlReader r = new XmlNodeReader(doc.DocumentElement);
m_dataSet = (DataSet)xs.Deserialize(r);
mODP.ObjectInstance = m_dataSet;
}
The desired result is that the ListBox would have a TextBox for each element in the data structure. Note that the data structure is hierarchical for a reason. I cannot flatten the data structure to simplify the problem.
I am certain that the xml data is being loaded correctly into the data structure, because I can place a breakpoint and check it and all the data looks fine. But when I run the program, nothing shows up in the ListBox.
Any help is appreciated.
I figured out the things I was doing wrong. There were several things wrong. Here are the main points:
1) I should have been using an itemsControl
2) I didn't need to use HierarchicalDataTemplate
3) I needed three levels of controls: a TextBox inside an itemsControl inside an itemsControl... and this can be accomplished using two DataTemplates. The top-level itemsControl refers to a DataTemplate that holds an inner itemsControl. That itemsControl then refers to a DataTemplate that holds an inner TextBox.
And here is the correct xaml:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="TheData" />
<DataTemplate x:Key="ElementTemplate">
<TextBox Text="{Binding Path=df_myValue}"/>
</DataTemplate>
<DataTemplate x:Key="ElementSetTemplate">
<GroupBox Header="I am a GroupBox">
<ItemsControl ItemsSource="{Binding Path=df_elementSet}" ItemTemplate="{StaticResource ElementTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Source={StaticResource TheData}, Path=df_basicSet}" ItemTemplate="{StaticResource ElementSetTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Hope this helps someone else...
Related
XAML:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:Message110FirstView DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
VIEWMODEL:
public ObservableCollection<ViewModelBase> Messages
{
get { return GetValue<ObservableCollection<ViewModelBase>>(MessagesProperty); }
set { SetValue(MessagesProperty, value); }
}
public static readonly PropertyData MessagesProperty = RegisterProperty("Messages", typeof(ObservableCollection<ViewModelBase>), null);
My question relates to this part of xaml:
<Views:Message110FirstView DataContext="{Binding}"/>
So, how to make different views in this place.
Thank you.
If I understand you correctly, then you want change view based in viewmodel.
It is appropriate to use DataTemplates if you want to dynamically switch Views depending on the ViewModel:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModelA}">
<localControls:ViewAUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelB}">
<localControls:ViewBUserControl/>
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding CurrentView}"/>
</Window>
If Window.DataContext is an instance of ViewModelA, then ViewA will be displayed and Window.DataContext is an instance of ViewModelB, then ViewB will be displayed.
The best example I've ever seen and read it is made by Rachel Lim. See the example.
I want to use some mock data to design my DataTemplate. How do I set a mock ObservableCollection as the ItemsSource of my ItemsControl, considering I'm using d:DataContext on it to point to a mock class containing said collection?
Here is what I have so far:
<DataTemplate x:Key="MyTemplate">
<Grid Margin="5,5,5,5">
<CheckBox Content="{Binding Name}" />
</Grid>
</DataTemplate>
<ItemsControl d:DataContext="{d:DesignInstance Type=mocks:MyViewModelMock, IsDesignTimeCreatable=True}" ItemsSource="{Binding MyMockList}" ItemTemplate="{StaticResource MyTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public class MyViewModelMock {
public ObservableCollection<MyModel> MyMockList { get; set; }
public MyViewModelMock() {
MyMockList .Add(new MyModel() { Name = "Mock 1" });
MyMockList .Add(new MyModel() { Name = "Mock 2" });
}
}
Point you d:DataContext at a static implementation of that type and you will have your design time data context. Here is a good example http://adamprescott.net/2012/09/12/design-time-data-binding-in-wpf/
In this scenario, an array of resources are sent to the ViewModel.
The objective is to display these resources as buttons in a WrapPanel in the view.
At this moment, I'm doing this using the C# code below. However, I'd like to do this in the Xaml side. Eventually, I'd like to use DataTemplates to format the buttons with other properties of the Resource class.
What is the best way of approaching this? Thanks in advance.
public void SetResources(Resource[] resources)
{
WrapPanel panel = this.View.ResourcesPanel;
panel.Children.Clear();
foreach(Resource resource in resources)
{
var button = new Button
{
Tag = resource.Id,
Content = resource.Content,
Width = 300,
Height = 50
};
button.Click += this.OnResourceButtonClick;
panel.Children.Add(button);
}
}
A common way to display a variable set of items would be to use an ItemsControl. You would bind the ItemsControl's ItemsSource property to an ObservableCollection of your Resource objects.
<UserControl ...>
<UserControl.DataContext>
<local:ViewModel/>
</UserControl.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Click="OnResourceButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel class might look like this:
public class ViewModel
{
public ViewModel()
{
Resources = new ObservableCollection<Resource>();
}
public ObservableCollection<Resource> Resources { get; private set; }
}
You may access the ViewModel instance in the UserControl or MainPage code by casting the DataContext:
var vm = DataContext as ViewModel;
vm.Resources.Add(new Resource { Id = 1, Content = "Resource 1" });
...
Sample Application:
The sample application that the supplied code belongs to displays a list of Vehicle objects via Binding. The Vehicle class is a top level class that subclasses can derive from e.g. Car and Bike. The sample application at the moment displays the owner's name of the Vehicle.
Sample Model code:
public class Vehicle
{
private string _ownerName;
public string ownerName
{
get { return _ownerName; }
set { _ownerName = value; }
}
}
public class Car : Vehicle
{
public int doors;
}
public class Bike : Vehicle
{
// <insert variables unique to a bike, ( I could not think of any...)>
}
UserControl XAML Code:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
UserControl code behind:
public List<Vehicle> vehicleList = new List<Vehicle>();
public CustomControl()
{
InitializeComponent();
createSomeVehicles();
list.DataContext = vehicleList;
}
public void createSomeVehicles()
{
Car newcar = new Car();
newcar.doors = 5;
newcar.ownerName = "mike";
Bike newbike = new Bike();
newbike.ownerName = "dave";
vehicleList.Add(newcar);
vehicleList.Add(newbike);
}
What I want to be able to do:
I would like to be able to display a button in the list object dependant upon the Type of the Vehicle object. E.g. I would like to display a Open Boot button within the list item for Car's; Type Bike does not have a boot and so no button would display within the list item.
Idea's on how to accomplish this:
I have looked into the custom binding of different DataTemplates based upon what type of object it is. E.g. from the code behind I could call:
object.Template = (ControlTemplate)control.Resources["templateForCar"];
The problem here is that I am using a Binding on the whole list and so there is no way to manually bind a DataTemplate to each of the list items, the list binding controls the DataTemplate of it's items.
You can create a DataTemplate for each Bike and Car (and for any CLR type). By specifying the DataTemplate's DataType property, the template will automatically be applied whenever WPF sees that type.
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
<Button Content="Open Boot" ... />
</WrapPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bike}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" />
</Grid>
I have an observablecollection of Images that get populated via the following code:
<StackPanel Orientation="Horizontal" Grid.Column="0">
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" SelectedItem="{Binding CurrentItem}" />
</StackPanel>
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}"
Margin="9,0,0,0" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
However the Content Control is supposed to bind to the BigImageView via an ObservableCollection
BigImage = new ObservableCollection<Image>();
_listView = CollectionViewSource.GetDefaultView(BigImage);
_listView.CurrentChanged += new EventHandler(OnCurrentChanged);
public System.ComponentModel.ICollectionView BigImageView
{
get
{
return _listView;
}
set
{
_listView = value;
OnPropertyChanged("BigImageView");
}
}
I want to return the image to the content control when I move the listbox. I have been racking my brain and trying everyhitn but it does not work. any help would be appreciated.
There is no need to bind the selecteditem, the collectionview should take care of that.
Try this:
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
<ContentControl.ContentTemplate>
1
Create a viewmodel with a list and a selected item:
public class BigImageViewModel : INotifyPropertyChanged
{
private string bigImage;
//string for path?
public ObservableCollection<string> BigImageView {get; set; } //Of course, make sure it has a value
public string SelectedBigImage
{
get { return bigImage; }
set { bigImage = values; NotifyPropertyChanged("SelectedBigImage"); }
}
}
Set this object on the DataContext of your control in the constructor:
DataContext = new BigImage(); //Make sure you initialize your list
Set the ListBox ItemsSource to your BigImage list, bind your SelectedItem to BigImageView
and use that in your content control:
<ListBox ItemsSource="{Binding BigImageView}" SelectedItem={Binding SelectedBigImage} />
ContentControl:
<ContentControl Name="Detail" Content="{Binding SelectedBigImage, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>
2
Or screw that view model:
Set the list directly in the constructor (after the InitializeComponent() ):
myListBox.ItemsSource = ObservableCollection<string>(); //Make sure you initialize your list with whatever your object is..
Give the list a name:
And bind with an ElementName binding to your selected item:
<ContentControl Name="Detail" Content="{Binding ElementName=myListBox, Path=SelectedItem}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>