I have a custom Panel where I declared a custom property to hold the content (I don't want to use Children for the content):
[ContentProperty(Name = "PanelContent")]
public class CustomPanel : Panel
{
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent",
typeof(Collection<UIElement>), typeof(CustomPanel),
new PropertyMetadata(new Collection<UIElement>(), null));
public Collection<UIElement> PanelContent
{
get
{
return (Collection<UIElement>)GetValue(PanelContentProperty);
}
}
}
This works perfectly when used like this:
<CustomPanel>
<TextBlock>A</TextBlock>
<TextBlock>B</TextBlock>
</CustomPanel>
But when I want to use the panel as an ItemsPanelTemplate inside ItemsControl, the ContentProperty attribute is ignored and adds everything to the Children collection, not the PanelContent collection:
<ItemsControl ItemTemplate="{StaticResource ReviewTemplate}" ItemsSource="{Binding Reviews}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<CustomPanel></CustomPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This is not how it should work. According to the documentation:
An ItemsPanelTemplate object element should contain exactly one FrameworkElement-derived class that serves as the root element for items. In most cases this is a Panel-derived class. The expanded template serves as the parent for the realized items and there generally is more than one item. Therefore the XAML content property of the intended root element of an ItemsPanelTemplate should support a collection, as Panel.Children does.
The Panel's GenerateChildren method, that is responsible for this task looks (as seen in ILSpy) like
internal virtual void GenerateChildren()
{
IItemContainerGenerator itemContainerGenerator = this._itemContainerGenerator;
if (itemContainerGenerator != null)
{
using (itemContainerGenerator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
{
UIElement uIElement;
while ((uIElement = (itemContainerGenerator.GenerateNext() as UIElement)) != null)
{
this._uiElementCollection.AddInternal(uIElement);
itemContainerGenerator.PrepareItemContainer(uIElement);
}
}
}
}
As you can see, it always adds to this._uiElementCollection, which is the field backing the Children property.
I think the ItemsPanelTemplate can only take Panel.Children as the target to layout items. In fact, if you derive your CustomPanel from, say, ContentControl, you will find following exception message in metro style app:
Exception: The ItemsControl.ItemsPanelTemplate must have a derivative of Panel as the root element.
The documentation you linked might be a documentation bug which was copied from WPF time, when you still can insert visuals programmatically into visual tree. In Metro app, there's no longer a way to put your own list of UIElements into visual tree without using Panel.Children.
Because ItemsPanelTemplate is used by ItemsControl to create and use the Panel and it's not XAML that does anything. ItemsControl will simply call Panel.Children.Add no matter what ContentProperty is set to.
Panel is supposed to only layout the children and never style them. So all you must do os only override Arrange and Measure methods. All panels only do that.
For styling and any other logic you must use another approach.
Related
I'm trying to improve performance with my WPF application and I'm having problems with a complex ItemsControl. Although I've added Virtualization, there's still a performance problem and I think I've worked out why.
Each item contains a series of expandable areas. So the user sees a summary at the start but can drill down by expanding to see more information. Here's how it looks:
As you can see, there's some nested ItemsControls. So each of the top level items has a bunch of Hidden controls. The virtualization prevents off-screen items from loading, but not the hidden items within the items themselves. As a result, the relatively simple initial layout takes a significant time. Flicking around some of these views, 87% of time is spent parsing and Layout, and it takes a few seconds to load.
I'd much rather have it take 200ms to expand when (if!) the user decides to, rather than 2s to load the page as a whole.
Asking for advice really. I can't think of a nice way of adding the controls using MVVM however. Is there any expander, or visibility based virtualization supported in WPF or would I be creating my own implementation?
The 87% figure comes from the diagnostics:
If you simply have
- Expander
Container
some bindings
- Expander
Container
some bindings
+ Expander
+ Expander
... invisible items
Then yes, Container and all bindings are initialized at the moment when view is displayed (and ItemsControl creates ContentPresenter for visible items).
If you want to virtualize content of Expander when it's collapsed, then you can use data-templating
public ObservableCollection<Item> Items = ... // bind ItemsControl.ItemsSource to this
class Item : INotifyPropertyChanged
{
bool _isExpanded;
public bool IsExpanded // bind Expander.IsExpanded to this
{
get { return _isExpanded; }
set
{
Data = value ? new SubItem(this) : null;
OnPropertyChanged(nameof(Data));
}
}
public object Data {get; private set;} // bind item Content to this
}
public SubItem: INotifyPropertyChanged { ... }
I hope there is no need to explain how to to do data-templating of SubItem in xaml.
If you do that then initially Data == null and nothing except Expander is loaded. As soon as it's expanded (by user or programmatically) view will create visuals.
I thought I'd put the details of the solution, which is pretty much a direct implementation of Sinatr's answer.
I used a content control, with a very simple data template selector. The template selector simply checks if the content item is null, and chooses between two data templates:
public class VirtualizationNullTemplateSelector : DataTemplateSelector
{
public DataTemplate NullTemplate { get; set; }
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
{
return NullTemplate;
}
else
{
return Template;
}
}
}
The reason for this is that the ContentControl I used still lays out the data template even if the content is null. So I set these two templates in the xaml:
<ContentControl Content="{Binding VirtualizedViewModel}" Grid.Row="1" Grid.ColumnSpan="2" ><!--Visibility="{Binding Expanded}"-->
<ContentControl.Resources>
<DataTemplate x:Key="Template">
<StackPanel>
...complex layout that isn't often seen...
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="NullTemplate"/>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<Helpers:VirtualizationNullTemplateSelector Template="{StaticResource Template}" NullTemplate="{StaticResource NullTemplate}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Finally, rather than using a whole new class for a sub-item, it's pretty simple to create a "VirtualizedViewModel" object in your view model that references "this":
private bool expanded;
public bool Expanded
{
get { return expanded; }
set
{
if (expanded != value)
{
expanded = value;
NotifyOfPropertyChange(() => VirtualizedViewModel);
NotifyOfPropertyChange(() => Expanded);
}
}
}
public MyViewModel VirtualizedViewModel
{
get
{
if (Expanded)
{
return this;
}
else
{
return null;
}
}
}
I've reduced the 2-3s loading time by about by about 75% and it seems much more reasonable now.
This simple solution helped me:
<Expander x:Name="exp1">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp1, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<Expander x:Name="exp2">
<Expander.Header>
...
</Expander.Header>
<StackPanel
Margin="10,0,0,0"
Visibility="{Binding ElementName=exp2, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
An easier way to achieve this is to change the default Visibility of the contents to Collapsed. In this case WPF won't create it initially, but only when a Trigger sets it to Visible:
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility"Value="Visible" TargetName="ExpandSite"/>
</Trigger>
Here "ExpandSite" is the ContentPresenter within the default ControlTemplate of the Expander control.
Note that this has been fixed in .NET - see the default style from the WPF sources on github.
In case you have an older version, you can still use this fixed control template to update the old one with an implicit style.
You can apply the same technique to any other panel or control.
It's easy to check if the control was already created with Snoop. Once you attached it to your application, you can filter the visual tree with the textbox on the top left. If you don't find one control in the tree, it means it was not created yet.
Basically, I'm not sure how to use MVVM, and/or use commands correctly in my current situation. So, I have a View, containing a list box, and a panel of animation objects, that I created. These animation objects can be animated through a simple public method, Animate(). The goal here, is to associate this Animate() method with buttons inside the list box, like so:
As we can see in the diagram below, both the ListBox items and the visual elements inside of the animation area are associated with the same collection of models from the ViewModel, with the items in each being templated. For example, the ListBox items are simply defined to have some text related to a data item, and the AnimationObjects take on an appearance according to the data. These models, I feel, should not understand that an animation is occurring - they're simple data, and the animation does not change them.
Finally, I show in the below diagram, that I have created two FrameworkElement child types, one for holding animation objects, and another that defines these animation objects.
How can I connect this animation action to the buttons within the list box? It doesn't make sense to me that the models/viewmodels know about the animation, because it doesn't change the state of anything in my application - it's just for visual purposes. I've thought about using a RoutedCommand defined in AnimationObject, and having the buttons bind their command property accordingly, but I worry that will simply make every element animate at the same time.
It is also important for my sake, that I conform to MVVM, as these data will be used in many other situations, perhaps even a different version of this view.
Any advice would be appreciated.
What you can do is call a command in your ViewModel , i.e. the DataContext of your ListBox.
CS :
public class ViewModel
{
public ICommand AnimateObjectCommand { get; }
}
XAML :
<DataTemplate x:Key="AnimationObjectItemTemplate">
<Button Command="{Binding Path=DataContext.AnimateObjectCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
</DataTemplate>
<ListBox ItemsSource="{Binding AnimationObjects}" ItemTemplate="{StaticResource AnimationObjectItemTemplate}"/>
your Command implementation should be one that accepts an argument which would be passed by the CommandParameter .
private ICommand _animateObjectCommand;
public ICommand AnimateObjectCommand
{
get
{
if (_animateObjectCommand == null)
{
_animateObjectCommand = new RelayCommand<AnimationObject>( ao => { ao.Animate(); });
}
return _animateObjectCommand;
}
}
The CommandParameter = {Binding} meaning this.DataContext where this is an Item in your ListBox and it's DataContext is an AnimationObject.
I'm using several lists across my project instead of trees - for proper virtualization (a lot of items in tree structure).
Those lists are pretty much the same. The only difference is in DataTemplates. Those lists have a few events bound, which I have to copy & update in several places. Current events are used to:
prevent horizontal auto-scrolling
support for arrow keys to navigate through tree structure
I found no way to bind events in a single style in resource dictionary, as events must belong to specific class. So I have to copy exactly same events between classes and bind them to specific lists. That is quite a lot of text, both in XAML and code.
What I wanted to do is to define a new user control, deriving EVERYTHING from standart ListBox, but overriding a few minor methods (instead of events). And reuse this control everywhere where I need such a list without having to copy all the events.
Problem is - it requires me to define custom <UserControl ... />. Is there a way to just use ListBox template/style there? I need no GUI modifications from standart ListBox.
I could be missing some simple way to perform what I want. I'd appreciate any way to do this.
Not sure about your setup but you will probably have to override the ListBox and ListBoxItem. Then override some methods :
public partial class MyListBox: ListBox
{
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new MyListBoxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyListBoxItem;
}
}
public class MyListBoxItem : ListBoxItem
{
}
This will force your containers to be ListBoxItem overrides.
Now you just have to implement yous specific code tof keys in ListBoxItem overrides. If you don't need any style changes the default ListBox style will be applied.
Now you can use it in your XAML:
<local:MyListBox ItemsSource="{Binding Items}">
<local:MyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding id}"/>
</DataTemplate>
</local:MyListBox.ItemTemplate>
</local:MyListBox>
I am using the Visifire charts to display data on a Windows Phone 7 application.
I created a chart that was properly bound to a dependency property. It worked great. I decided to make the chart into a user control, since I was going to use it in another project as well with the same setup. Now my databinding doesn't work unless I bind it in the code behind rather than in the XAML.
here's what I have:
<UserControl ... x:Name="root">
...
<chart:DataSeries ... DataSource="{Binding ElementName=root, Path=Results}">
...
</UserControl>
and the code behind:
public MyList Results
{
get { return (MyList)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
// Using a DependencyProperty as the backing store for Results. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ResultsProperty =
DependencyProperty.Register("Results", typeof(MyList), typeof(MyChart), new PropertyMetadata(null));
public GoogleChart()
{
Loaded += delegate
{
// theChart.Series[0].DataSource = Results;
};
Results = new GoogleResults();
InitializeComponent();
}
If I uncomment the line theChart.Series[0].DataSource = Results; it works perfectly. But if I leave that line commented (like I had before I moved the chart to the UserControl) it doesn't bind. (By the way: theChart is the x:name of the parent of the chart. So the first element, .Series[0], references to the chart).
Does anyone know why this would happen? Again, it worked great until I moved the code to a UserControl.
Thanks
If I'm understanding you correctly you've created this UserControl so that you can place instances of it into various pages in your app.
In that case you are likely to be giving those instances a name. That name will replace the name "Root" that is initially assigned in the UserControl's Xaml. Hence the binding for ElementName=Root will fail.
Typically there is a root element (normally a Grid) with the name "LayoutRoot". Hence instead of relying on the UserControl name which can change use "LayoutRoot" which by convention is the Content element for the UserControl. Like so:-
<chart:DataSeries ... DataSource="{Binding ElementName=LayoutRoot, Path=Parent.Results}">
Note the property path now starts with Parent which takes you up to the UserControl without actually needing to know the UserControl's name.
I have a parent contentcontrol which displays data via a datatemplate. The datatemplate contains a stackpanel with several usercontols of the same type. I like to set the property only once on the parent control, it must set the value of the property on all the subcontrols. But if there is a way to do it on the stackpanel it's also OK. The template can be changed at runtime and the values need also to be propagated to the new template.
My current solution is to implement the property on both parent and subcontrol and use code to propagate the value from the parent to all the subcontrols. My question is: is there a better or other ways of doing this?
EDIT:
Some notes of clarification to my question. The application is currently WPF, but if it's portable to silverlight it would be a bonus. The property is a dependency of the type Style.
I want to use it to style part of the subcontrol. Currently the datatemplate is stored in a separate resource dictionary, so it can be reused. The visuals of the subcontrol are styled via controltemplate. The template contains three different controls, the first one is a label. The need (desire, foolish wish) is to set the style only once, to give the label on all the subcontrols in the datatemplate a consistent look and feel.
So the crux of the problem is to override the value of the a style dependency property on a subcontrol, stored in a resource dictionary from a container control. Both are custom user controls, so all options are open.
<Parent SubSubStyle="x" Template="template" />
<DataTemplate x:Key=template>
<StackPanel>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
<Subcontrol SubSubStyle="?"/>
</StackPanel>
</DataTemplate>
Is the property that you're trying to set a DependencyProperty that you have created? If so, the ideal thing to do in WPF is to define the property such that it will be inherited by elements in the visual tree.
If it's not your own dependency property (or if you're using Silverlight which does not support this mechanism) then you should instead use implicit styles.
public class MyControl {
// be prepared for some dependency property hell below
// this defines a DependencyProperty whose value will be inherited
// by child elements in the visual tree that do not override
// the value. An example of such a property is the FontFamily
// property. You can set it on a parent element and it will be
// inherited by child elements that do not override it.
public static readonly DependencyProperty MyInheritedProperty =
DependencyProperty.Register(
"MyInherited",
typeof(string),
typeof(MyControl),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.Inherits
)
);
}
Style is best option http://msdn.microsoft.com/en-us/library/ms745683.aspx#styling_basics