In my WPF application I have an Observablecollection "CollOfPersons" of Persons, where each Person Object has a property "NotesOnPerson" of type
List<Notes>
(among other properties). Now I bind "CollOfPersons" to a listbox lb in code via
lb.ItemsSource = CollOfPersons;
Now I have set up a template how to display a person, namely I wrap each person in a 'Expander' and display the basic properties (e.g., Name, Age) in Expander.header, and this works fine, e.g.,
<Expander.Header>
<StackPanel Orientation="Horizontal">
...
<TextBlock Text="{Binding Path=Name}"/>
...
</StackPanel>
</Expander.Header>
However, now I'd like to bind the NotesOnPerson list of notes to the Expander.Content. But since this is again a list of varying size I don't know how to do it. Same strategy as above does not work, because I don't know the name of the Expander (as I knew the name 'lb' of the big listbox in which all the stuff is). Something like
<Expander.Content>
<ListBox ItemTemplate="{StaticResource NoteTemplate}"
ItemsSource="{Binding Path=NotesOnPerson}"/>
</Expander.Content>
doesn't seem to work. I seem to be confused about code and XAML binding. How should I solve this?
Is this what you're looking for?
<Expander.Content>
<ListBox ItemTemplate="{StaticResource NoteTemplate}"
ItemsSource="{Binding NotesOnPerson}"/>
</Expander.Content>
I'm not familiar with the Expander, but since NotesOnPerson is (presumably) a property of Person and not of Name, that's the syntax you should use. (the Path= is optional, since just putting it in like that is another way to declare the Path)
Related
I have a list of parameters in an Observable Collection in my ViewModel that I want to be displayed as textboxes or comboboxes dependent on the datatype, but I'm struggling to find a way to implement any kind of conditional functionality in XAML.
So, say I have an observable collection of parameters (title, datatype, value):
Valid,Boolean,
Name,String,
Age,UInt,
I currently have these generating a sequence of textboxes in the window next to labels indicating the type and parameter name using:
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Textbox Text="{Binding Value}"
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But I don't want this to be a textbox every time; I want to be able to have something like an "if" or a "switch" to make a combobox instead if the datatype facilitates pre-defined responses like True/False.
I've seen similar posts where the answers seemed to suggest using Triggers or DataTriggers to do this, which looks exactly like what I need. However, they always seem to put it in a Style, with Style.Triggers, rather than any of the elements I currently have. Any attempts to replicate what I've seen have failed due to slight differences in what we've used; The example will use something like ContentControl and bind the style with "Content = {…}" which I can't do because "Content" isn't something I can apply to "ItemControl", but I need ItemControl due to the way it implements ObservableCollection in a way Content control doesn't.
Is there a simple way to put "If datatype == Boolean, make a combobox here" sort of thing? If I can make this happen in the ViewModel, I'm open to that too. Don't want to be doing anything in the code-behind, as MVVM doesn't seem to play nice with it and has always bit me in the behind later down the road when I try to mix them and I can't get my values to interact properly.
Would very much appreciate any help.
I realise I could just have the user TYPE true/false and interpret it in the viewmodel as Boolean, but there's several reasons this won't really work in my case. For one thing, there will be many, many options.
Really think you should be using DataTemplates:
... xmlns:sys="clr-namespace:System;assembly=mscorlib"
<ItemsControl ItemsSource="{Binding Parameters}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type sys:Boolean}">
<Checkbox IsChecked="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<ContentPresenter Content="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can use your own types as well that it will auto detect and use the datatype for.
How can I change the Text of a TextBlock when the selection in my ListView changes?
I don't want do this manually...
All Items of the ListView are LogEntry's (class)... Can I use Binding in the Text-Attribute of the TextBlock to get a specific property of the selected Item?
Yes, in fact there are multiple solutions, i give you the most "WPF" like answer, but imo also the least flexible.
First you need to set the IsSynchronizedWithCurrentItem="True" property
Now if you select an item, the bound CollectionView will set the item as the CurrentItem.
Now your TextBox/Block can bind to this specific item via a special binding syntax using a '/'.
For Example:
<TextBlock Text="{Binding LogEntries/}"/>
of course you can get a specific property from the current item via binding aswell
<TextBlock Text="{Binding LogEntries/WarningMessage}"/>
Hope that helps.
assuming you have a listview like this:
<ListView ItemSource="{Binding LogEntries}" Name="logs" IsSynchronizedWithCurrentItem="True">
</ListView>
<ContentControl Content="{Binding ElementName=logs, Path=SelectedItem}" ContentTemplate="{StaticResource logTemplate}"/>
Now you need to provide that logTemplate in the Resources.
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:LogEntry}">
<TextBlock Text="{Binding Path=LogText}"/> <-- This is a Property-Binding of your custom class
</DataTemplate>
</UserControl.Resources>
The last thing missing is to provide the namespace to your local class LogEntry. If you use an awesome tool like Resharper, it will insert the namespace for you. Otherwise, here a sample declaration:
<UserControl xmlns:local="clr-namespace:My.App.Namespace.LogEntry;assembly=My.App"
... (rest of namespace declarations)
I have a TreeView databound to a CollectionViewSource Groups collection. This is so that I can display the data using the power of the CollectionViewSource, and the data itself also has a hierarchical structure, which is why I need the TreeView. I have a second control which is bound to the SelectedItem of the TreeView.
The problem is that when the group header is selected, the program crashes with the following exception.
{"A TwoWay or OneWayToSource binding cannot work on the read-only property 'Name' of type 'MS.Internal.Data.CollectionViewGroupInternal'."}
The objects in my TreeView contain a Name property that is two way bound in another control. The binding engine seems to find the Name property for the Group and attempt to bind to that. How can I prevent this exception from occuring? I would like for the rest of my program to treat it as if nothing is selected when the group header is selected, or disallow selecting the group header all together. Below is a simplified version of the code.
<TreeView
x:Name="CustomersTree"
ItemsSource="{Binding CustomersViewSource.Groups}"
ItemTemplate="{StaticResource CustomerGroupsTemplate}">
<MyUserControl DataContext="{Binding ElementName=CustomersTree, Path=SelectedItem, Mode=OneWay}" />
<HierarchicalDataTemplate x:Key="CustomerGroupsTemplate" ItemsSource="{Binding Path=Items}" ItemTemplate="{StaticResource CustomerTreeItemTemplate}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="CustomerTreeItemTemplate" ItemsSource="{Binding Customers}">
<StackPanel>
<Image Source="{Binding ImageSource}" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
To be clear, the error is a result of the binding in the CustomerGroupsTemplate as far as I can tell, and changing this binding to OneWay results in the same error. The information in the tree shows up the way it is expected to, it is only when a group header is selected that the exception occurs.
The problem was the result of a two way binding within the user control. I ended up using a converter to check for the type of the object being bound, ignoring it if it wasn't the object I wanted.
Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>
You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"
Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?
Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)
I have a data layer which is returning lists of classes containing data. I want to display this data in my form in WPF. The data is just properties on the class such as Class.ID, Class.Name, Class.Description (for the sake of example)
How can i create a custom control or template an existing control so that it can be given one of these classes and display its data in a data-bound fashion.
Thanks :)
You could use a ListBox and set its ItemsSource property to the list containing your data items. Then you define a DataTemplate for your type like this:
<DataTemplate x:Key="MyDataTemplate" DataType="{x:Type MyType}">
<StackPanel>
<TextBlock Text="{Binding ID}"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
</DataTemplate>
...and tell the ListBox to use this DataTemplate by setting the ItemTemplate property.
It is also sufficient to just define the DataTemplate as above and give it no key. Then it will be used for all items which have the respective type.
BTW: You can find a more detailed example in MSDN on the page for the ItemTemplate property.