I am attempting to write my first implementation of databinding in a WPF form.
I have got to the point where I can successfully retrieve data to populate my combobox with the required entries but am stuck on how I would go about populating a dependent ListView given the current selection of the Combobox.
To explain my problem better this is the current view backing my application.
class SchoolBookViewModel
{
public SchoolBookViewModel() { }
//Problem is here... How do I pass in the SelectedSchoolClassID?
public CollectionView ClassBookListEntries(Guid SelectedSchoolClassID)
{
return new CollectionView(SchoolQuoteList.GetSchoolClassQuoteListBySchoolClassID(SelectedSchoolClassID, 2011, "MyConnectionString"));
}
public CollectionView SchoolEntries
{
get
{
return new CollectionView(SchoolList.GetSchoolList("MyConnectionString"));
}
}
}
And this is the XAML
<StackPanel Height="449" HorizontalAlignment="Left" Margin="12,12,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="650" DataContext="{Binding}">
<Label Content="School:" Height="28" Name="lblSchoolName" Width="651" />
<ComboBox Height="23" Name="cmbSchoolNames" Width="644" DisplayMemberPath="SchoolName" ItemsSource="{Binding Path=SchoolEntries}" SelectedValuePath="SelectedSchoolClassID" SelectionChanged="cmbSchoolNames_SelectionChanged" />
<Label Content="Class booklist:" Height="29" Name="label1" Width="651" />
<ListView Height="163" Name="lblClassBookList" Width="645" ItemsSource="{Binding Path=ClassBookListEntries}" DisplayMemberPath="QuoteReference" />
</StackPanel>
And in the Window_Loaded method I call
stackPanel1.DataContext = new Views.SchoolBookViewModel();
Am I even on the right track? Any guidance would be appreciated.
To get the ComboBox's selection back into the ViewModel you need a property to bind to one of its selection properties. You can also get rid of the explicit CollectionViews if you're not doing anything with them. By just binding to the collections directly, ICollectionView instances will be created and managed for you automatically. Try structuring your VM like this:
class SchoolBookViewModel : INotifyPropertyChanged
{
private SchoolList _selectedSchoolClass;
public SchoolList SelectedSchoolClass
{
get { return _selectedSchoolClass; }
set
{
_selectedSchoolClass = value;
ClassBookListEntries = SchoolQuoteList.GetSchoolClassQuoteListBySchoolClassID(_selectedSchoolClass.Id, 2011, "MyConnectionString");
NotifyPropertyChanged("SelectedSchoolClass");
}
}
private SchoolQuoteList _classBookListEntries;
public SchoolQuoteList ClassBookListEntries
{
get { return _classBookListEntries; }
set
{
_classBookListEntries = value;
NotifyPropertyChanged("ClassBookListEntries");
}
}
private SchoolList _schoolEntries;
public SchoolList SchoolEntries
{
get
{
if (_schoolEntries == null)
_schoolEntries = SchoolList.GetSchoolList("MyConnectionString");
return _schoolEntries;
}
}
...
}
In general it's better to not set explicit Width and Height values and instead let the layout system size elements for you. You can also get rid of the DataContext="{Binding}" - this is redundant as DataContext is inherited and {Binding} means the value of the DataContext itself. Here's the XAML cleaned up and bound to the new properties from above:
<StackPanel HorizontalAlignment="Left" Margin="12,12,0,0" x:Name="stackPanel1" VerticalAlignment="Top">
<Label Content="School:" x:Name="lblSchoolName" />
<ComboBox x:Name="cmbSchoolNames" DisplayMemberPath="SchoolName" ItemsSource="{Binding Path=SchoolEntries}"
SelectedItem="{Binding SelectedSchoolClass}" />
<Label Content="Class booklist:" x:Name="label1" />
<ListView x:Name="lblClassBookList" ItemsSource="{Binding Path=ClassBookListEntries}" DisplayMemberPath="QuoteReference" />
</StackPanel>
Related
I have a custom UserControl called ReferencedItem. It should take a Guid called ItemId. It is implemented as such:
private static void OnItemIdChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs dpArgs)
{
//Do something
}
public static readonly DependencyProperty ItemIdProperty = DependencyProperty.Register("ItemId", typeof(Guid?), typeof(ReferencedItem), new FrameworkPropertyMetadata(
// use an empty Guid as default value
Guid.Empty,
// tell the binding system that this property affects how the control gets rendered
FrameworkPropertyMetadataOptions.AffectsRender,
// run this callback when the property changes
OnItemIdChanged
));
public Guid? ItemId
{
get { return (Guid?)GetValue(ItemIdProperty); }
set { SetValue(ItemIdProperty, value); }
}
public ReferencedItem()
{
InitializeComponent();
ViewModel = new ReferencedItemCtrlViewModel();
DataContext = ViewModel;
}
The ItemsSource will be made up of Reference objects defined as:
public class Reference
{
public Guid Id { get; set; }
}
Now when binding this ReferencedItem the value is not set as intended. Here is the code I want to work, but does not bind as intended:
<ItemsControl x:Name="ReferenceStack" ItemsSource="{Binding References}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ReferencedItem ItemId="{Binding Id}" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have tried:
<local:ReferencedItem ItemId="128d48f0-f061-49fb-af49-b8e4ef891d03" Height="30" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
This works as expected, the OnItemIdChanged method is triggered.
<Label Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="30" Width="90"/>
This works as expected, a label is rendered with the Id.
Is there something I'm missing here? From what I can tell the data is available at bind time -- it just doesn't bind under the exact conditions I need it to :)
Thanks for any input!
EDIT:
Here is the code-behind for ReferencedItemList, the first block of XAML posted above:
public partial class ReferencedItemList : UserControl
{
protected ReferencedItemListCtrlViewModel ViewModel;
public ReferencedItemList()
{
InitializeComponent();
ViewModel = new ReferencedItemListCtrlViewModel();
DataContext = ViewModel;
}
public void Load(Guid id, string name)
{
ViewModel.Load(id, name);
//ReferenceStack.ItemsSource = ViewModel.References;
}
}
The commented line has been experimented with in place of the ItemsSource="{Binding References}" that was defined in the XAML.
I don't think I can successfully post the code for ReferencedItemListCtrlViewModel without going down a rabbit hole -- needless to say it has a property References of type ObservableCollection<Reference> where Reference is defined earlier in this post.
ReferencedItem.xaml:
<v:BaseUserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</v:BaseUserControl.Resources>
<StackPanel Orientation="Horizontal">
<Image x:Name="LinkIcon" Visibility="{Binding HasReference, Converter={StaticResource BooleanToVisibilityConverter}}" ToolTip="View Referenced Item" Source="/Images/link.png" Height="18" MouseUp="LinkIcon_MouseUp"/>
<TextBlock x:Name="ReferencedObjectDesc" Text="{Binding ReferenceHierarchy}" FontStyle="Italic" VerticalAlignment="Center" />
</StackPanel>
I just wanted to post the answer (explanation) I came across.
The problem was changing the DataContext of my ReferencedItem user control in the constructor. The view would instantiate a ReferencedItem and alter the DataContext - so once it was time to bind I had already flipped the context from the intended Reference.
There are multiple ways to resolve the timing - all project dependent. Either avoid setting the DataContext all together, set it post binding, or change the context on other items as appropriate.
Much thanks to Sinatr, Andrew Stephens, and Mike Strobel for all mentioning this at one point or another -- just took me some time to actually reach it. I don't think there's a way to assign credit to a comment, but let me know if there is.
I'm new to wpf, I created a listbox it will create a dynamic listitems,Here I used datetemplate which contains two controls that is two textblocks, one textblocks contains binding a values form combobox(which is string datatype),The other one is, bind a value from code bind.
XAML
<ListBox ItemsSource="{Binding obj}" HorizontalContentAlignment="Left" x:Name="lstbxindex" SelectionMode="Extended" Foreground="White" FontSize="20px" Height="201" BorderBrush="#555555" Margin="80,40,0,0" VerticalAlignment="Top" Width="282" Background="#555555" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="5" >
<TextBlock Height="40px" Width="80px" Text="{Binding roundedhourvalue, FontSize="24" Background="#555555" Foreground="White"></TextBlock>
<TextBlock x:Name="items" Text="{Binding}" Margin="35,0,0,0"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C# (Roundedhour.cs)
public class Roundedhour
{
public string hourvalue { get; set; }
public override string ToString()
{
return string.Format("{0}", hourvalue);
}
}
In this class create a property for hourvalue. For this class I created a object in codebehind file which I mentioned below.create a object and assign a value for hourvalue variable.
C# (Code Behind)
{
if (dispatcherTimer1.Interval == TimeSpan.FromSeconds(15))
{
//lstbxindex.Items.Add(lstbxindex.SelectedItem.ToString());
string hrvalue = Convert.ToString(hrvalueinitially);
obj = new Roundedhour();
obj.hourvalue = Convert.ToString(hrvalueinitially);
string roundedhourvalue =obj.hourvalue;
this.DataContext = this;
//lblprojectAhr.Content = string.Join(",", hrvalueinitially + "" + "hr");
}
}
Here, I created a object for Rounderhour class.Assign values to that property hour value. But I cannot be bind a value from codebind to XAML page.
Your ItemsSource should be of an CollectionType.
ListBox ItemsSource="{Binding obj}"
You should also start to give your variables and properties meaningful names. That makes it easier to read your code later on.
The second Problem is in your Binding itself.
You are binding like this: Text="{Binding roundedhourvalue}
So WPF is expecting a property roundedhourvalue on obj.
But as shown in your CodeBehind there is only obj.hourvalue.
So change your Binding to Text="{Binding hourvalue}
Check your Output-Window for details.
NOTE:
string roundedhourvalue = obj.hourvalue;
has no getter and is not accsessible since its private.
NOTE: You either use a Binding OR your set the ItemsSource in CodeBehind.
Try it like this:
Just remove all the formatting and stuff:
<ListBox ItemsSource="{Binding RoundedHours}" x:Name="ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="5" >
<TextBlock Text="{Binding hourvalue}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And change your code-behind to this:
private void UpdateDataContext(object hrvalueinitially)
{
List<Roundedhour> hours = new List<Roundedhour>();
hours.Add(new Roundedhour()
{
hourvalue = hrvalueinitially.ToString()
});
//Set the ItemsSource in code: => remove your ItemsSource Binding from XAML
listBox.ItemsSource = hours;
}
OR your can use an 'MVVM' approach:
public class MyViewModel : INotifyPropertyChanged
{
//IMPLEMENT INotifyPropertyChanged HERE PLS
public ObservableCollection<RoundedHour> Collection { get; set; } = new ObservableCollection<RoundedHour>();
private void AddToCollection(object hrvalueinitially)
{
Collection.Add(new RoundedHour()
{
hourvalue = hrvalueinitially.ToString()
});
OnPropertyChanged("Collection");
}
//Make sure to set your Windows DataContext to an Instance of this Class
}
Assign XAML object's "ItemsSource" property with your binding variable.
Also it's totally wrong binding object's itself into object's property like
this.DataTemplate = this;
Use:
List<yourobject> bindingObjectList = new List<yourobject>();
// insert your objects into the list
this.ItemsSource = bindingObjectList;
Here you can find an example:
Grid & Pivot Binding Example for multiple DataTemplates
I'm using WPF and have a TreeView on my form binding to model. Objects have attributes and I want to bind a selected item(in tree view) attributes to a listbox but I can't figure out how to this. My code is:
Bar class:
public class Bar
{
string barName;
List<bar> children;
List<Foo> attrs;
public string BarName
{
get { return barName; }
set { barName = value; }
}
public List<Folder> Children
{
get { return children; }
set { children = value; }
}
public List<Foo> Attributes
{
get { return attrs; }
set { attrs = value; }
}
public Bar(string name)
{
barName = name;
children = new List<Bar>();
attrs = new List<Foo>();
attrs.Add(new Foo { Name = "Attr1: " + name });
attrs.Add(new Foo { Name = "Attr2: " + name });
attrs.Add(new Foo { Name = "Attr3: " + name });
}
}
Foo class:
public class Foo
{
public string Name { get; set; }
}
Filling model:
Bar bar = new Folder("bar1");
bar.Children.Add(new Bar("bar1.1"));
bar.Children[0].Children.Add(new Bar("bar1.1.1"));
bar.Children.Add(new Bar("bar2"));
this.DataContext = bar;
And also XAML:
<Window.Resources>
<ResourceDictionary>
<HierarchicalDataTemplate DataType="{x:Type local:Bar}"
ItemsSource="{Binding Children}">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding BarName}"
Foreground="Black"
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.Column="1"/>
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView Height="162" HorizontalAlignment="Left" Margin="203,0,0,0" Name="treeView1" VerticalAlignment="Top" Width="288" ItemsSource="{Binding Children}"/>
<ListBox Height="100" HorizontalAlignment="Left" Margin="203,168,0,0" Name="listBox2" VerticalAlignment="Top" Width="288" ItemsSource="{Binding Name}"/>
</Grid>
Now TreeView binding works fine and I there is a Bar.Name displayed, but ListBox is empty. Please, explain me, what should I do?
You're binding your listbox to Name, so it's trying to find a property called "Name" in your Bar class. What I believe you're actually trying to do is show the attributes for the item that's currently selected in the TreeView. So bind to the TreeView's SelectedItem and set DisplayMemberPath to "Name":
<ListBox Height="100" HorizontalAlignment="Left" Margin="203,168,0,0" Name="listBox2" VerticalAlignment="Top" Width="288" ItemsSource="{Binding SelectedItem.Attributes, ElementName=treeView1}" DisplayMemberPath="Name"/>
This will work, but a better idea would be to create a member in you Bar class for the currently selected item (e.g. "CurrentTreeItem") and bind both the treelist's SelectedItem and the ListBox's source items to that property, that way at least you can put breakpoints in the setter etc and make sure your front end controls are firing properly. The problem with doing this though is that you don't seem to be supporting IPropertyChange notification (if you don't know what that is then drop what you're doing and hit Google before going any further).
You can bind your listbox directly to TreeView selected item by changing xaml to
<TreeView Height="162" HorizontalAlignment="Left" Margin="203,0,0,0" Name="treeView1" VerticalAlignment="Top" Width="288" ItemsSource="{Binding Source}" />
<ListBox DisplayMemberPath="Name" Height="100" HorizontalAlignment="Left" Margin="203,168,0,0" Name="listBox2" VerticalAlignment="Top" Width="288" ItemsSource="{Binding Path=SelectedItem.Attributes, ElementName=treeView1}"/>
Sadly, tree view's selected item is read only property, so you can't easily create a property in you view and bind both listbox and treeview to it.
Also you should change your population to
public ObservableCollection<Bar> Source { get; set; }
public MainWindow()
{
InitializeComponent();
Bar bar = new Bar("bar1");
bar.Children.Add(new Bar("bar1.1"));
bar.Children[0].Children.Add(new Bar("bar1.1.1"));
bar.Children.Add(new Bar("bar2"));
Source = new ObservableCollection<Bar>() { bar };
this.DataContext = this;
}
Be aware thought, that you have to implement INotifyPropertyChanged interface to allow bindings to update automatically
I am trying to populate an Observable Collection Based on What items are selected from the AutoCompleteBox. May I get some guidance towards where I am going wrong. It's not Binding the way I would like it to.
xaml:
<telerik:RadAutoCompleteBox x:Name="RadAmortAutoBox" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="6" VerticalAlignment="Top" SelectedItems="{Binding AmortDates, Mode=OneWay}"
ItemsSource="{Binding Payments}" DisplayMemberPath="PaymentNo" TextSearchPath="PaymentNo" Width="708" />
ViewModel:
public ObservableCollection<int> amortDates;
public ObservableCollection<int> AmortDates
{
get { return amortDates; }
set
{
amortDates = value;
RaisePropertyChanged("AmortDates");
}
}
I have the following XAML:
...
<ListBox Name ="RoomsListBox" Height="100"
HorizontalAlignment="Left" Margin="12,41,0,0"
VerticalAlignment="Top" Width="120"></ListBox>
...
And the following C#-code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
RoomsListBox.ItemsSource = new[] { new { Name = "First1" },
new { Name = "First2" } };
RoomsListBox.DisplayMemberPath = "Name";
}
The problem is that my ListBox have items but they are empty. Why I don't see "First1" and "First2" instead?
The issue here isn't with the bindings nor the ItemTemplate nor the change notification. It's the Anonymous Type you're using that's causing it. try using a class or struct for your items
public class Item
{
public string Name { get; set; }
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
RoomsListBox.ItemsSource = new[] {
new Item { Name = "First1" },
new Item { Name = "First2" }};
RoomsListBox.DisplayMemberPath = "Name";
}
your xaml stays the same, or you can define a DataTemplate for the ListBox items if you want. Note that you can't set both the ItemTemplate and DisplayMemberPath at the same time (one has to be null). Also, make sure that the class representing your items has to be public.
Hope this helps :)
Just a thought..
Have you tried settings the DisplayMemberPath property in XAML? There might be an issue with the order of calls.
You have to set DisplayMemberPath property on your ListBox to Name.
Moving forward you might want to consider creating a DataTemplate for your items to have more control:
<ListBox x:Name ="RoomsListBox" Height="100"
HorizontalAlignment="Left" Margin="12,41,0,0"
VerticalAlignment="Top" Width="120">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
See this tutorial for more info: http://weblogs.asp.net/scottgu/pages/silverlight-tutorial-part-5-using-the-listbox-and-databinding-to-display-list-data.aspx
I would prefer that you define your binding in your xaml and for example in your Code-Behind you define a property for the items of your listbox.
Example: (xaml)
<ListBox Name ="RoomsListBox"
ItemsSource="{Binding MyItems}"
Height="100"
HorizontalAlignment="Left"
Margin="12,41,0,0"
VerticalAlignment="Top"
Width="120" />
Example: (C# in your code-behind)
//...
private ObservableCollection<string> _myItems;
public ObservableCollection<String> MyItems
{
get
{
return _myItems ?? (_myItems = new ObservableCollection<string> { "FirstItem", "SecondItem"});
}
set
{
_myItems = value;
}
}
Like ChrisF said you could use the INotifiyPropertyChanged Interface, there you would raise the PropertyChanged event in the setter of your property.
See --> http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx