I'm recently exploring a sample of using the GridViewRowPresenter inside a TreeView to make it like a "treegrid". To do this one generally need to define a GridViewColumnCollection, and set it to GridViewRowPresenter.Columns explicitly. The sample works for me very well.
However since GridViewRowPresenter is most often used with ListView and GridView. I begin to wonder how does the ListView and GridView setting the Columns property for each row. The xaml most likely like this:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Length" DisplayMemberBinding="{Binding}"/>
</GridView>
</ListView.View>
<ListViewItem>Hello</ListViewItem>
<ListViewItem>World</ListViewItem>
</ListView>
Note that though the GridViewColumnCollection is defined explicitly, it's not set anywhere for the ListViewItem. Eventually each ListViewItem will contain a GridViewRowPresenter, but in no where the Columns property is set. I checked the open source code for ListView.cs, ListView.xaml, ListViewItem.cs, ListViewItem.xaml, GridViewRowPresenter.cs GridViewRowPresenterBase.cs, in none of these the Columns property is set.
The closest thing I found is GridView.ColumnCollection property, I suspect it played some role in connecting the GridViewRowPresenter.Columns and GridView.Columns, but how?
GridView.ColumnCollection is the thing you seem to be looking for.
GridViewRowPresenter holds the same instance of ColumnCollection and once arranging its cells it runs through the list of columns for additional information such as desired width or desired DisplayMemberBinding.
There is also a GridViewHeaderPresenter and it does the same as RowPresenter just instead of creating cells per line it creates headers.
Edit:
You can see once you reflector the code following statement.
protected internal override void PrepareItem(ListViewItem item)
{
base.PrepareItem(item);
// attach GridViewColumnCollection to ListViewItem.
SetColumnCollection(item, _columns);
}
That is how GridViewColumnCollection is being passed/attached to ListViewItem.
Its internal as you can see.
I will show how to probe into the GridViewColumn and GridViewRowPresenter interactions at runtime. Consider the following simplified XAML:
<ListView x:Class="demo_listview"
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wd="clr-namespace:System.Windows.Documents;assembly=PresentationFramework"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<!-- ... -->
<ListView.View>
<GridView>
<!-- ... -->
<GridViewColumn x:Name="my_gv_column" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="my_textblock" Text="ABCD" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- ... -->
</GridView>
</ListView.View>
</ListView>
Now in code, override a method such as ListView.ClearContainerForItemOverride as shown below. The code will modify the background color of the TextBlock to make it red:
Note that the red color persists after "Clearing" due to containers being recycled. I picked that function to keep the demo simple, since it ensures that the templates will be fully instantiated for examination. Otherwise, for example, if you get 'null' for gv_row, for example, you need to call lvi.ApplyTemplate() immediately prior.
I assume you are able to get the ListViewItem easily enough for the row you're interested in. So from any fully-instantiated ListViewItem at runtime, we can now see how to get to the contents of a particular instantiated CellTemplate at runtime. Additional remarks follow below the code.
public partial class demo_listview : ListView
{
protected override void ClearContainerForItemOverride(DependencyObject el, object item)
{
base.ClearContainerForItemOverride(el, item);
var lvi = (ListViewItem)el;
var gv_row = (GridViewRowPresenter)lvi.Template.FindName("5_T", lvi);
var ix = ((GridView)View).Columns.IndexOf(my_gv_column);
var cp = (ContentPresenter)VisualTreeHelper.GetChild(gv_row, ix);
var tb = (TextBlock)my_gv_column.CellTemplate.FindName("my_textblock", cp);
tb.Background = Brushes.Red;
}
};
The first step is to get the GridViewRowPresenter from the ListViewItem. There are a few ways to do this. Above, I use the magic constant "5_T" to get the instantiation from the template name. This constant is as defined by WPF, so it will work as long as you are using the standard ListViewItem its built-in control template.
Next we need the index of the column we're interested in. The column instance itself is easily accessed in C# by giving it a name (my_gv_column) in the XAML. As for its index, I'm not sure if the IndexOf technique shown here will continue to work if columns are reordered. If not, the correct result would be obtained by fetching, via reflection, the value of the private _actualIndex field (or ActualIndex property) from the GridViewColumn of interest.
Next, using VisualTreeHelper.GetChild, get the visual child of the GridViewRowPresenter which corresponds to the desired column index; this will be the ContentPresenter for the cell! With this, you can do another FindName search within the template to find the actual instantiated control you're looking for, by name. In the example, we successfully get the TextBlock instance and then change its background color to demonstrate the result.
It's done in FrameworkElementFactory.ApplyAutoAliasRules:
// GridViewRowPresenter auto-aliases Content and Columns to Content
// property GridView.ColumnCollection property on the templated parent.
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/FrameworkElementFactory.cs,9ce27c5244be816c,references
So, GridViewRowPresenter.Columns defaults to {TemplateBinding GridView.ColumnCollection}.
Related
I have a GridView inside a ListView in a WPF application. Upon clicking the column header the list items get sorted alphabetically.
XAML:
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="200">
<GridViewColumn.HeaderContainerStyle>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="cal:Message.Attach" Value="[Event Click]=[Action Sort($dataContext, 'Name')]"/>
</Style>
</GridViewColumn.HeaderContainerStyle>
</GridViewColumn>
And here's the sorting method in my ViewModel:
public void Sort(ShellViewModel dataContext, string propertyName)
{
// sorting logic
}
That works perfectly fine. But instead of passing 'Name' (or whatever the header is called), I'd like that to be a property.
I've tried Value="[Event Click]=[Action Sort($dataContext, $eventArgs.PropertyName)]" but that doesn't work and returns null.
You might want to consider changing the listview to a datagrid.
A datagrid has column click sorting built in. No code would be necessary. You'd need to change your markup to a datagrid, which might be non trivial of course. There may be some reason to pick listview instead of datagrid.
If that doesn't suit then the current approach looks like it is going to be tricky.
Looking at the msdn article about sorting a listview. That handles the event.
The piece of code finding the propertyname is:
var headerClicked = e.OriginalSource as GridViewColumnHeader;
.....
var columnBinding = headerClicked.Column.DisplayMemberBinding as Binding;
var sortBy = columnBinding?.Path.Path ?? headerClicked.Column.Header as string;
You might be able to get the binding from $eventArgs. if you use enough dots.
However, I think you would need to get the path out the binding in code.
I personally wouldn't be so keen on a viewmodel doing that sort of manipulation.
I'd suggest a behaviour if you want to encapsulate it or "just" do the sorting entirely in the view. Very much like
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/how-to-sort-a-gridview-column-when-a-header-is-clicked?view=netframeworkdesktop-4.8
Maybe you don't want the up down arrows and some other aspects.
If the viewmodel needs to know what the user picked you could add an attached property and bind that to the viewmodel. Setcurrentvalue on that property in the view.
Similarly, you could make that twoway. Handle change of the dependency property and set sort if the viewmodel needs to initially set sort dynamically.
Alternatively, extend the attached property concept. Put a sort togglebutton in the headers and bind to an observabledictionary. One entry per column. Handle property changed in the value of the observabledictionary and apply sort.
I have a BindingList<Pair<string,string>> (where Pair is just your bog standard generic to contain two related objects). I want to be able to bind this to a ListView such that the .First values is in column 1 and the .Second value is in column 2. Further to this, how can I make sure the list view constantly represents the contents of the list, such that if I change one of the strings the ListView automatically updates?
Here is my Pair<TI,TJ> class for reference:
public class Pair<TI, TJ>
{
public TI First;
public TJ Second;
public Pair(TI first, TJ second)
{
First = first;
Second = second;
}
}
Just to clarify, a BindingList is just the collection I'm currently trying to use, I can use any collection capable of supporting this functionality and holding Pair<string,string>.
I don't have the IDE at hand, but it depends mostly on if you use WinForms or XAML/WPF. Since it is probably the last (more modern), some tips:
Probably you need an ObservableCollection, if something changes in the collection the bindings will work.
For binding to your data, you need to bind them in WPF, and set a data context.
In general it looks like this:
<ListView ItemsSource="{Binding ListOfYourData}"
SelectedItem="{Binding Path=SelectedItem}"
...
<ListView.View>
<GridView>
<GridViewColumn Width="140" Header="First"
DisplayMemberBinding="{Binding First}"
<GridViewColumn Width="140" Header="Second"
DisplayMemberBinding="{Binding Second}"
There is two important interfaces for databinding with Winforms.
IBindingList - This one concern "Collection" and will update the control when a new element is added or when an element is removed. The BindingList already implement it so you don't have to take care about. You could test with a simple List if you want to test the behaviour without this interface.
INotifyPropertyChanged - This one concern "Object" and will update the control when an object have a change one of his properties (the value of the object has changed). Your class "Pair" should implement this interface. It is trivial. You juste have to add a Sub PropertyChanged() which raise a event and add a call of this Sub in the setter of your properties.
You could have a look at the MSDN if you want a sample.
https://msdn.microsoft.com/fr-fr/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx
Resume :
No IBindingList => Control doesn't update when you add or remove object in a collection.
No INotifyPropertyChanged => Control doesn't update when a property of an object have changed.
Edit - Of course the control have to support Binding which is not the case of the legacy ListView...
Question
Basically I would like to do the following but it seems that I cannot:
UserControl myControl = new UserControl();
DataTemplate template = new DataTemplate(myControl);
The question: Is it possible to construct a DataTemplate from UserControl instance? If not, are there any other possible solutions?
Real problem
I'm working on a project where majority of UI views are simple static Word-like documents (e.g some text fields and maybe some images, nothing too fancy). Because most of persons working on this project are not coders we have designed very simple in-house markup language for UI generation. An example of markup of simple view is following:
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
Now these templates are loaded at runtime and usercontrols are created based on them. In this case one usercontrol would be created and it would contain simply couple of stack panels and text blocks so that resulting control would look a bit like text document. XAML equivalent would be something like:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name: "/>
<TextBlock Text={Binding Person.FirstName}
</StackPanel>
<StackPanel>
...
</StackPanel>
...
</StackPanel>
Then, I started to implement support for lists but couldn't think of a way how to do that. In theory it is simple and I came up with following syntax (+ XAML equivalent):
[List Customers]
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
[EndList]
->
<StackPanel>
<ItemsControl ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
[Insert code from previous XAML example here]
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
But I can't do that because it seems that I cannot construct DataTemplate directly from UserControl instance. There would be no problems if my UserControls were types, but they are not.
One possible solution is that I could bind ItemsControl.Items directly to list of UserControls (instead of binding to ItemsSource and ItemTemplate) but this is sub-optimal in our case for couple of reasons. I would be willing to try some other solutions first!
Update
For clarification: all I have is plain instance of UserControl class which contains the content I need. E.g.
UserControl control = new UserControl();
var panel = new StackPanel();
panel.Children.Add(...);
panel.Children.Add(...);
control.Content = panel;
// How to use that control as ItemTemplate for ItemsControl?
// It seems that it is not possible directly but I want to
// know what my options are.
I don't have class for it because I'm constructing it at run-time and I don't want to create new type dynamically by emiting IL code because it is way too painful.
Creating a datatemplate from Code behind goes like this:
FrameworkElementFactory factory = new FrameworkElementFactory(MyUserControl.GetType());
DataTemplate dt = new DataTemplate();
dt.VisualTree = factory;
yourItemsControlInstance.ItemTemplate = dt;
A datatemplate is a definition of controls to be built at runtime, that is way this construction with a ElementFactory. You do not want the same instance of the UserControl for every item in your ItemsControl.
Ah I understand your problem now. I don't think there is an easy way (one or two lines of code) to create a datatemplate from a UserControl instance.
But to solve your problem I see two directions:
At the point where an usercontrol is created, create a datatemplate instead and use that. It will be cumbersome, with nested FrameworkElementFactories. I have never done that, and the MSDN documentation says that you may encounter some limitations you cannnot do compared to datatemplates in Xaml. But if it is simple it must be doable. There used to be a codeproject article by Sacha Barber you could use as a guidance (if needed).
You pack the creation of the UserControl in a method called private UserControl createMyUserControl(){}
And do something like this:
ItemsControl itemsControl = new ItemsControl();
foreach (var customer in Customers)
{
var usercontrol = createMyUserControl(...);
usercontrol.DataContext = customer;
itemsControl.Items.Add(usercontrol);
}
Second option is less elegant in my opinion, so I would check out the option 1 first.
WPF: How to create Styles in code/and magical Content (see section at the end for extensive sample of a DataTemplate in Code behind)
I think you can replace UserControl with the ContentControl.
Just set the content of the ContentControl to the desired template and use it as ItemTemplate for the ItemsControl.
To date every ListView I've had I just set ItemSource={Binding} in my Xaml and then in the .CS file I say listview.datacontext = myobject and the view loads just fine. But now I need to have a list that updates as the data updates as well. So after some research I discovered ObservableCollections and rewrote my code to use that. But I can't get my data to display when setting the listview to my dataobject.
My Xaml:
<ListView ItemsSource="{Binding Tests}" Name="DataCompareTests" Margin="0,0,5,0" Grid.Column="0">
<ListView.View>
<GridView>
<GridViewColumn Header="TestCase" Width="200" DisplayMemberBinding="{Binding name}" />
</GridView>
</ListView.View>
</ListView>
My Xaml.cs:
readonly DataCompare dataCompare = new DataCompare();
public void Execute_Click(object sender, RoutedEventArgs e)
{
var Tests = new ObservableCollection<TestCases>();
Tests = dataCompare.LoadTestCases(); //located in another class file
//DataCompareTests.DataContext = Tests;
}
If I remove the "Tests" part of the binding in my Xaml and remove the comments from the .DataContext line above, the view displays the correct information. However it's my assumption that if I want my view to update as the data does I need to specify my object in the binding. How do I properly set that? I can't seem to find the correct answer.
Thanks,
Jason
I think you need to familiarize yourself a little better with bindings and object oriented programming in general.
If you set your datacontext to your model object, ".Tests" should be a public property of that model object. Also, don't do this:
var someVariable = new SomeClassThatTakesWorkToConstruct();
someVarialbe = someOtherVariable.SomeMethod();
What you meant to do was this:
var someVariable = someOtherVariable.SomeMethod();
This is for 2 good reasons 1) You are not wasting the construction of an ObservableCollection. 2) Your code will be easier to refactor (the type returned by SomeMethod can change without you having to alter your declaration of someVariable).
Edit, additional resources:
Databinding Overview
You've got a path specified but no source for the binding specified.
MVVM Article
Great article on using the common MVVM WPF pattern, helps you keep your code object oriented, clean, etc. even with complex UI interaction.
It would appear my concerns were pointless and I WAS doing this the proper way in the first place.
According to MSDN:
"However, if you are binding to an object that has already been created, you need to set > the DataContext in code, as in the following example.
...
myListBox.DataContext = myDataSet;"
My object was already created, and I did set the DataContext in the code. All I had to do was leave the ListView ItemSource as {Binding} and each time I added to the Tests object, the list updated..
I can't believe I spent an entire day doubting I was doing this correctly without moving forward to check. :-)
I have a ListView that allows the user to change the ViewBase through a Context Menu (it acts like a simplified version of windows explorer).
<ListView Name="lv" Grid.Row ="0" Grid.Column ="1" >
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="View1" Click="SwitchViewMenu"/>
<MenuItem Header="View2" Click="SwitchViewMenu"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<local:View1 />
</ListView.View>
</ListView>
The ViewBases DataTemplates are defined in a Generic.XAML file, and i use the following Function to change the chosen view :
void ChangeView(string str)
{
if (str == "View1")
{
lv.View = lv.FindResource("View1") as ViewBase;
}
else if (str == "View2")
{
lv.View = lv.FindResource("View2") as ViewBase;
}
}
The problem:
I got a custom CheckBox control in all the DataTemplates, that has a predefined click event attached, however when i try to move up the Parents of the CheckBox, the highest level i can reach is the Parenting Grid in the DataTemplate in use.
What i need to access is the Parent Window itself.
Note:
I tried to add a Dependency Property to the Custom CheckBox Control and bind it to an extra defined variable in the sent object (The data template's items DataType object) that had a window reference as its value, but i kept getting null even though all the other dependency properties/values got bound.
It depends on how you're trying to get to the parents. I highly recommend this article (Josh Smith) to get a real understanding of what's going on in WPF.
That said, you could try Window.GetWindow(myControl); (static method); it should work for any pure WPF trees (for interop with WinForms, see this).