WPF DataTemplate ComboBox binding issue - wpf

Edit: Rewritting question
I use the Project Management Library from http://dlhsoft.com/Home.aspx in my WPF usercontrol.
I'm displaying their LoadChartResourceListView control on my page and use a datatemplate to display custom columns in a list view:
<my:LoadChartResourceListView TaskManagerSource="{Binding ElementName=ganttChartTaskListView,Path=TaskManager}"
TimelinePageStart="{Binding TimelinePageStart, ElementName=ganttChartTaskListView, Mode=TwoWay}"
TimelinePageFinish="{Binding TimelinePageFinish, ElementName=ganttChartTaskListView, Mode=TwoWay}"
DisplayedTime="{Binding DisplayedTime, ElementName=ganttChartTaskListView, Mode=TwoWay}"
Margin="6" Name="loadChartResourceListView">
<my:LoadChartResourceListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource ListViewColumnHeaderContainerStyle}">
<!-- Set CellTemplate (or CellTemplateSelector) to specify column templates. -->
<GridViewColumn Header="Group" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="85" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type inf:MEFUserControl}}, Path=DataContext.ResourceGroups}"
DisplayMemberPath="GroupName"
SelectedValuePath="GroupID" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Resource">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="myTB" Text="{Binding Content}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The whole user control (inf:MEFUserControl) that contains this LoadChartResourceListView has a datacontext set to an instance of my viewmodel class (TaskData). Within the TaskData class is a ObservableCollection<ResourceGroup> ResourceGroups {get;set;}. Each ResourceGroup has an int GroupID {get;set;} and string GroupName{get;set;}.
Also, within the TaskData class is an ObservableCollection<Resource> Resources {get;set;} ... each Resource has a int GroupID{get;set;}, string Content {get;set;} and ResourceGroup ResGroup{get;set;}
The above code works fine with displaying the combobox and the textbox... I cannot, for the life of me, figure out why I'm having issues binding to the SelectedValue property of the combobox. I've many things including SelectedValue="{Binding GroupID}"
Everytime I try to set the SelectedValue I receive this error popup in VS:
"A first chance exception of type 'System.Reflection.AmbiguousMatchException' occurred in mscorlib.dll" This is the error from the output window (its massive) http://pastebin.com/AGJwn00C
From reading, I've read that this is due to a parent object having a property with the same name "GroupID". I've renamed GroupID to ResGroupID in the Resource class, thinking that it conflicted with the ResourceGroup class, but I receive the same error.
When I set this ItemsSource, is the DataContext for the combobox being set to the UserControl or TaskData instance?
Update:
I receive the error also when I use a TextBox instead of a combobox:
<TextBox Text="{Binding GroupID}"/>

Just write
SelectedValue="{Binding Path=GroupID}"

Solved it. After reading this: http://dlhsoft.com/KnowledgeBase/Task-Appearance-Bar-Templating-Custom-Data-Binding.htm
I had to do Item.PropertyName for custom properties.

Related

Which components of a ListView have to be explicitly provided when utilising Control Templates?

The solution to my question might just be specifically related to WPF ListViews, but it could also just be a control template issue in general. I don't know.
I have the following 3 property class, with overridden ToString method defined in my codebehind:
public class WorkListViewItem
{
public int Id { get; set; }
public string Namey { get; set; }
public bool d { get; set; }
public override string ToString()
{
return "ID: " + Id + "\r\nName: " + Namey + "\r\nd?:" + d.ToString();
}
}
I have a working ListView defined in my markup, as well as Data Templates defined under the window's resources for all 3 class properties, and an array of WorkListViewItem defined under an all-parent grid's resources.
(DTNamey also has two-way binding, a textbox, and a TextChanged event attached, but that makes no difference to my issue)
Data Templates:
<DataTemplate x:Key="DTNamey" DataType="{x:Type local:WorkListViewItem}">
<TextBox Text="{Binding Path=Namey, Mode=TwoWay}" Width="65" Foreground="BlueViolet" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="DTId" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=Id}" Foreground="GreenYellow"/>
</DataTemplate>
<DataTemplate x:Key="DTd" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=d}" Foreground="BlueViolet"/>
</DataTemplate>
Data Array:
<x:Array Type="{x:Type local:WorkListViewItem}" x:Key="wvlis">
<local:WorkListViewItem Id="1" Namey="Fred" d="True"/>
<local:WorkListViewItem Id="2" Namey="Beef" d="True"/>
<local:WorkListViewItem Id="3" Namey="Pork" d="False"/>
</x:Array>
ListView:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}">
<!--This actually works-->
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
Now this all does exactly what I want it to:
(I lack the reputation to post images, but trust me, it really does!)
It displays the 3 columns and a row for each element in the data array.
Changes to the textboxes for Namey are reflected in the second column (Length), and all is well in the world.
The Issue:
However, that same ListView definition, when placed in a Control Template, displays no items. The columns display just fine, but no rows are shown.
Window Resources:
<!--This does not work-->
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
</ControlTemplate>
Inside Grid:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}" Template="{StaticResource cmonnow}">
</ListView>
My Efforts:
I have tried adding ItemsPresenter controls in the columns, which clashes with the column header definitions.
I've tried adding the ItemsPresenter under:
<ListView.Items>
<ItemsPresenter/>
</ListView.Items>
I've tried setting the ListView's data context to: (totally incorrect, I'm sure)
<ListView DataContext="{Binding local:WorkListViewItem}">
I've even tried defining the ListView's ItemTemplate property.
I have been working at figuring this out for 3 days, and decided to turn to the internet for help.
So my question would be; which ListView properties are required to display the items collection when using Control Templates?
Thank you for your time.
Matthew
Update 29/09/2015
I've found that placing ItemsPresenter and ContentPresenter elements directly under the ListView tag, adds blank items that are correctly styled via the data templates.
Breakthrough!
I have found that by binding the template's ListView's ItemsSource property, to the ItemsSource provided, all the items are displayed, and all columns are correctly populated.
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView ItemsSource="{TemplateBinding ItemsSource}">
...
</ControlTemplate>
Figured it out
Boiling this down, it seems that the only component that is required to get a ListView control template to display data is the ItemsSource property of the template ListView.
The ListView View property does not need to be manually set in order to see items.
However, for custom objects such as mine, it may be advisable to override the ToString method, as this is what you will see without any further template definitions.
(On a side note, it was a little counter-intuitive to have to define a ListView inside a Control Template with its TargetType property set to "ListView", but you could count this as a requirement too)

Bind to a Property in the Data Context from a ListviewItem

I have a data context in the form of MyViewModel.
MyViewModel has a property: public int MyWidth.
For each item in my listview, that is ListViewItem, I need to display a canvas with a width equal to MyWidth.
My listView has it's ItemSource bound to a property called MyCollectionOfInts.
Which, as you may have guessed, is of the following definition: ObservableCollection<int>.
The astute reader has likely realized, the data context of myListView is int and thus fails when trying to bind the non-existant MyWidth property from the int type data-context.
What kind of theoretical crazy binding is necessary to get this kind of nutty thing to work?
My most recent attempt was to bind using RelativeSource but couldn't exactly figure it out...
My list View:
<ListView Name="MyListView" ItemsSource="{Binding MyCollectionOfInts}"
My Items within the List view.
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumns Header=MyInts">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Label Name="m_TestLabel" Content="ASDF" />
<TextBox Text="{Binding Path=MyWidth, RelativeSource=????{RelativeSource AncestorType={x:Type MyViewModel}}}"/>
</DataTemplate>
</...a bunch of close brackets>
My list view Item is an int, but I want to get the Control's original data-context, ie. myViewModel, and bind the canvas width of my ListViewItem to the MyWidth property of myViewModel. How can I get the ListViewItem to recognize the control's data-context?
Note: I don't really want to make a container for the ListView and store a static MyWidth variable in it, but if that is the only way, then let me know. I'm hoping it's not.
Try this:
<TextBox Text="{Binding Path=DataContext.MyWidth, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
If your View is usercontrol else use Window in type
<TextBox Text="{Binding Path=DataContext.MyWidth,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}"/>
Or
<TextBox Text="{Binding Path=DataContext.MyWidth,
Source={x:Reference MyListView}"/>

How can I bind a control into a WPF listview column

I'm new to WPF so forgive me if I've missed something obvious
I have the following list view control (non relevent details removed for brevity)
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Details}"/>
</GridView>
</ListView.View>
</ListView>
I add items to this list view in code behind
public void Add(LogEntry entry)
{
ListViewItem item = new ListViewItem();
item.Content = entry;
listView.Items.Add(item);
}
where LogEntry has public string properties for "Type" and "Details"
So far, so good, all works lovely but..
I want my details property to be an element itself (TextBox or DockPanel containing various types of content)
How can I bind this Element to the list column?
Using the code above, Changing "Details" to an element I simply get the class name in the list box (e.g. System.Windows.Control.TextBox) as by default the cell displays the ToString() value of the property.
I have googled examples which use a DataTemplate but I can't find an example of binding an element to the content of a panel.
The control cannot be defined in xaml as its structure and contents are not known until runtime
The binding is one way (I only need to display the list, not update the underlying data structure)
To make my problem clearer, I want to bind the following list item
class LogEntry
{
public string Type {get;}
public DockPanel Details {get;} // This dock panel was created by code and contains
// various elements not predictable at compile time
}
Your Model, the LogEntry class, should not reference a UI control. Instead it should contain the data needed by the UI, and the XAML should define a DataTemplate that uses that data. For example,
public class LogEntry
{
public string Type {get;}
public ObservableCollection<IDetail> Details {get;}
}
<DataGridTemplateColumn Header="Details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<ItemsControl ItemsSource="{Binding Details}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You mentioned that the DockPanel in the LogEntry created items that were not known at runtime. Can you give an example of that? In most cases, you should have some kind of pattern in the data, and you can use DataTemplates to define how to draw each Detail piece.
<DataTemplate DataType="{x:Type local:LoginDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding MachineLoggedIn}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:LogoutDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding LoggedInTime}" />
</StackPanel>
</DataTemplate>
If you REALLY need to store a control in the Model, then you can use a ContentControl in your DataGridTemplateColumn and set it's Content equal to the Details
<ContentControl Content="{Binding Details}" />
Sorry I cant give you an exact answer as I dont have VS where I am right now here but a few pointers.
First instead of using your method to add Listview items you want to create an ObservableCollection with your data. Then you can bind the itemssource of your listview to the observableCollection.
Next you can create an itemtemplate for the listview containing the control you want, something quite simple would be like a stack panel with horizontal orientation and two textboxes.
Once you have done that because you have set the itemsource of the listview to the ObservableCollection you can just bind the textbox to the String property within your collection.
Note that ObservableCollection is better to bind to than List as ObservableCollection supports NotifyPropertyChanged().
I have copied your code into a new project and created the same list view here is the code. And this one worked and displayed to data correctly
XAML:
<ListView x:Name="MyList" ItemsSource="{Binding MyListDetails}">
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Lastname}"/>
</GridView>
</ListView.View>
ViewModel:
private List<Contact> _details= new List<Contact>();;
public List<Contact> MyListDetails
{
get
{
return _details;
}
set
{
_details = value;
this.RaisePropertyChanged("MyListDetails");
}
}
public void AddEntry(LogEntry entry)
{
MyListDetails.Add(entry);
}

Access control binding information from GridViewColumn

I have code like this
<GridViewColumn Header="Status" Width="75" DisplayMemberBinding="{Binding Path=TimesheetStatus}"/>
<GridViewColumn Header="Reviewed?" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="1,2,1,0" IsEnabled="{Binding Path=EnableReview}" IsChecked="{Binding Path=IsReviewed}" Checked="reviewedCheckBox_Checked"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
I am able to access the displaymemberbinding property (TimeSheetStatus) for Gridviewcolumn Status.
But I want to know how can I access the IsChecked Binding property (IsReviewed) from the "Reviewed?" Gridviewcolumn.
Any help?
because you are using a data template, there is no nice way to get there, but if you only wants to get to this specific column binding you can try debug the application and look at the column in debug view till you get the to checkbox, write this path in your code and use the BindingOperations helper class to get the binding of the relevant property in your check box

WPF Combobox DisplayMemberPath

Ok, I looked at other questions and didn't seem to get my answer so hopefully someone here can.
Very simple question why does the DisplayMemberPath property not bind to the item?
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" DisplayMemberPath="{Binding Name}" SelectedItem="{Binding Prompt}"/>
The trace output shows that it is trying to bind to the class holding the IEnumerable not the actual item in the IEnumerable. I'm confused as to a simple way to fill a combobox without adding a bunch a lines in xaml.
It simply calls the ToString() for the object in itemssource. I have a work around which is this:
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But in my opinion it's too much for such a simple task. Can I use a relativesource binding?
DisplayMemberPath specifies the path to the display string property for each item. In your case, you'd set it to "Name", not "{Binding Name}".
You are not binding to the data in the class, you are telling it to get it's data from the class member that is named by the member "name" so, if your instance has item.Name == "steve" it is trying to get the data from item.steve.
For this to work, you should remove the binding from the MemberPath. Change it to MemberPath = "Name" this tells it to get the data from the member "Name". That way it will call item.Name, not item.steve.
You should change the MemberPath="{Binding Name}" to MemberPath="Name". Then it will work.
You could remove DisplayMemberPath and then set the path in the TextBlock.
The DisplayMemberPath is really for when you have no ItemTemplate.
Or you could remove your ItemTemplate and use DisplayMemberPath - in which case it basically creates a TextBlock for you.
Not recomended you do both.
<TextBlock text="{Binding Path=Name, Mode=OneWay}"
Alternatively you don't need to set the DisplayMemberPath. you can just include an override ToString() in your object that is in your PromptList. like this:
class Prompt {
public string Name = "";
public string Value = "";
public override string ToString() {
return Name;
}
}
The ToString() will automatically be called and display the Name parameter from your class. this works for ComboBoxes, ListBoxes, etc.
Trying this :
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
from what i can figure,
"DisplayMemberPath" uses reflection to get the property name in the data context class, if it cant find it nothing will be displayed.
if class
class some_class{
string xxx{ get; }
}
DisplayMemberPath=xxx, will show whatever value "xxx" is
if you want to concatenate properties from the datacontext you need to create an item template, which will show up in the header and the drop down list.
<ComboBox.ItemTemplate>
<DataTemplate DataType="employee">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding first_name}" />
<TextBlock Text="" />
<TextBlock Text="{Binding last_name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
you cannot have "DisplayMemberPath" and "ComboBox.ItemTemplate" set at the same time.

Resources