Why Does ItemsControl Not Use My ItemTemplate? - wpf

I am able to use an ItemTemplate within an ItemsControl to render items in a specific format. However, if one of the items within the ItemsControl happens to be, say, a TextBox, that TextBox is rendered rather than an instance of the ItemsTemplate. From what I can tell, this is true for any FrameworkElement. Is this intended behavior for an ItemsControl, or am I doing something incorrectly?
An example:
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Rectangle Fill="Blue" Height="20" Width="20" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<sys:Object />
<TextBox />
<sys:Object />
<Rectangle Fill="Red" Height="20" Width="20" />
</ItemsControl.Items>
</ItemsControl>
I expected this to display four blue rectangles. I thought that any time an ItemTemplate has been defined each item in the collection is rendered as an instance of the template. However, in this case the following is rendered: a blue rectangle followed by a TextBox followed by a blue rectangle followed by a red rectangle.

The ItemsControl has a protected member IsItemItsOwnContainerOverride which is passed an object from the items collection and returns true if that object can be added directly to the items panel without a generated container (and thereby be templated).
The base implementation returns true for any object that derives from UIElement.
To get the behaviour you would expect you would need to inherit from ItemsControl and override this method and have it always return false. Unfortunately thats not the end of the matter. The default implementation of PrepareContainerForItemOverride still doesn't assign the ItemTemplate to the container if the item is a UIElement so you need to override this method as well:-
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}

I'm just speculating here, but I would bet that it's behavior that lives inside of the ItemContainerGenerator. I'd bet that the ItemContainerGenerator looks at an item, and if it's a UIElement it says, "cool, the item container's been generated, I'll just return it" and if it's not, it says, "I'd better generate a container for this item. Where's the DataTemplate?"

Related

Expander-like WPF control that only hides empty children

In my WPF view, I need something similar to an Expander or a TreeView, but instead of completely hiding the content, I only want to hide empty parts, i.e. TextBoxes with null or empty text, or empty ItemCollections.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
I would like to avoid doing this in the ViewModel, as that would need a property for each section (and I need lots of them), but it's purely visual and therefore IMHO it only belongs to the View.
So I guess the way to go is to use DependencyProperties or write some CustomControls, but I don't have an idea where to start. The XAML of the end result could look something like this:
<CustomExpander Header="Main" CollapseContentIfEmpty="True">
<CustomExpander Header="Section1" CollapseContentIfEmpty="True">
<StackPanel>
<TextBox Text="{Binding SomeString}" />
<TextBox Text="{Binding SomeEmptyString}" />
</StackPanel>
</CustomExpander>
<CustomExpander Header="Section2" CollapseContentIfEmpty="True">
<ListView ItemsSource="{Binding SomeCollectionView}" />
</CustomExpander>
</CustomExpander>
In this example, if CollapseContentIfEmpty is set to true and the CollectionView shows no elements (e.g. due to filters), only the content of SomeString should be visible, along with all the headers. If SomeString is empty, only "Main" should be visible, as now all child CustomExpanders are empty as well.
Setting CollapseContentIfEmpty to false (e.g. via a Button like in Expander) would show all Children again, regardless if they are empty or not.
I thought about using a style with a DataTrigger or set Visibility with a converter, but how would I link that to the parent's setting (e.g. IsExpanded)?
Use a binding with a {RelativeSource}.
In the following example, the TextBlock is invisible unless you set the Tag property of the parent UserControl to true:
<UserControl xmlns:sys="clr-namespace:System;assembly=mscorlib">
<UserControl.Tag>
<sys:Boolean>false</sys:Boolean>
</UserControl.Tag>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock Text="text..."
Visibility="{Binding Tag,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</UserControl>
You can of course replace the UserControl with a custom control with a custom bool property.
An Expander collapses its entire Content which is different from hiding specific controls in that content.

Change the layout of a TreeView to looks like multiple ListBox [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm trying to change the layout of a databound treeview from this:
To this:
And of course selection must works properly:
Do you have any ideas about how to do that. I've been trying to change the template but I can't find out a way to have this behavior. Maybe a component already exists...
Thanks for your help !
This is difficult. It seems to need a HierarchicalDataTemplate, but because the behavior you want requires multiple ItemsControls, it is not going to work as expected. I don't think there is a way to create a TreeView template in XAML that will do this. Your best bet is to create a custom items control of some sort. You will probably need to do the items binding in code, rather than in XAML, because without the HierarchicalDataTemplate the XAML has no way of understanding nested relationships.
That being said, if you are guaranteed to only have 2 levels of nesting (as in your example), you could do this easily with the following mark-up:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<ListBox Name="Level1" Width="150" Height="150"
ItemsSource="{Binding Collection}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level2" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level1, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level3" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level2, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
</StackPanel>
Where Collection is your root items collection and there is a property on each item called Children containing the child collection.
But I think what you are asking for is an items control that can support any number of nested levels, not just 2. So in that case, I would do this in code-behind. The binding will be the same- that is, at each level, the ListBox should be bound to the parent level's items. But you will obviously need to iterate and create one ListBox for each nested level.
I finally find a way out, but like you say Charlie, it involves creating ListBox:
I create a new CustomControl which inherits Control (I couldn’t use neither Selector or TreeView because I wouldn’t have been able to manage the SelectedItem property from the derived class)
In the template of this CustomControl is an ItemsControl. This ItemsControl has its ItemTemplate property set to a DataTemplate containing a ListBox.
The CustomControl has a Depth property of type int. This property indicates the number of ListBox that should be generated.
The CustomControl automatically databound ListBoxes together: each ListBox’s ItemsSource property is databound to the SelectedItem’s children property of the previous ListBox in the visual tree.
The CustomControl has a SelectedItem property and a SelectionChanged event (like Selector-derived class).
I added an IsReallySelected attached property to the ListBoxItem which are generated. This enables to databing an IsSelected property of the ViewModel class behind the control with the IsSelected of the ListBoxItem. I had to create an attached property because its value is true when the ListBoxItem is selected AND the parent ListBox has IsSelectionActive set to true.
I blogged about this solution (with source code) on my blog.
Its too bad I didn't notice this question before you went to all that work. It is easy to restyle a TreeView to appear this way: The only code required is a single very simple attached property, "VisibleWhenCurrentOf".
The way to do it is to:
Style TreeViewItem to include a ListBox in its ControlTemplate outside the ItemsPresenter.
Control the visibility of the TreeViewItem template using "VisibleWhenCurrentOf", so that a given item is only visible inside the ItemsPresenter if it is the current item within the ListBox.
Restyling details
Here is the XAML for the relevant templates:
<ControlTemplate TargetType="TreeView">
<DockPanel>
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
<ControlTemplate TargetType="TreeViewItem">
<DockPanel
local:VisibilityHelper.VisibleWhenCurrentOf="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor,HeaderedItemsControl,2}">
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
These two templates are identical except for the conditional visibilty. The way this works is that the "+" in front of the tree item becomes a ListBox, and all items except the one selected in the ListBox are hidden.
Your BoxesTreeViewBoxStyle should set a margin around the ListBox so they will space correctly. You can actually simplify this further by putting the ListBox property values in the style, but I find it more convenient to set them in the ControlTemplate so I can restyle the ListBox without having to remember these settings.
Attached property
Here is the code for the VisibleWhenCurrentOf attached property:
public class VisibilityHelper : DependencyObject
{
// VisibleWhenCurrentOf
public static object GetVisibleWhenCurrentOf(DependencyObject obj) { return (object)obj.GetValue(VisibleWhenCurrentOfProperty); }
public static void SetVisibleWhenCurrentOf(DependencyObject obj, object value) { obj.SetValue(VisibleWhenCurrentOfProperty, value); }
public static readonly DependencyProperty VisibleWhenCurrentOfProperty = DependencyProperty.RegisterAttached("VisibleWhenCurrentOf", typeof(object), typeof(VisibilityHelper), new UIPropertyMetadata
{
PropertyChangedCallback = (sender, e) =>
{
var element = sender as FrameworkElement;
if(e.OldValue!=null)
{
var oldView = e.OldValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
oldView.CurrentChanged -= UpdateVisibilityBasedOnCurrentOf;
if(e.NewValue==null) element.DataContextChanged -= UpdateVisibilityBasedOnCurrentOf;
}
if(e.NewValue!=null)
{
var newView = e.NewValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
newView.CurrentChanged += UpdateVisibilityBasedOnCurrentOf;
if(e.OldValue==null) element.DataContextChanged += UpdateVisibilityBasedOnCurrentOf;
}
UpdateVisibilityBasedOnCurrentOf(sender);
}
});
static void UpdateVisibilityBasedOnCurrentOf(object sender, DependencyPropertyChangedEventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender, EventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender)
{
var element = sender as FrameworkElement;
var source = GetVisibleWhenCurrentOf(element);
var view = source==null ? null : source as ICollectionView ?? CollectionViewSource.GetDefaultView(source);
var visible = view==null || view.CurrentItem == element.DataContext;
element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
}
There is nothing complex here: Any time DataContext or the view's Current changes, visibilty is recomputed. The PropertyChangedCallback simply sets event handlers to detect these conditions and the UpdateVisibiltyBasedOnCurrentOf handler recomputes visibility.
Advantages of this solution
Since this solution is a real TreeView:
You get all the selection handling functionality for free.
It works with any number of tree levels.
You can use all the features of HierarchicalDataTemplate, including HeaderTemplate and HeaderTemplateSelector
You can use different ItemsSource bindings at each level rather than every collection requiring a "Children" proerty
It is a lot less code than a custom control

Rendering a non UIElement via binding

If I have an object derived from System.Windows.DispatcherObject but defines a ControlTemplate.
public class A : System.Windows.DependencyObject
{
public ControlTemplate ControlTemplate {get; set;}
}
which is a member of
public class B
{
public A NonUIElement {get; set;}
}
Is it possible to render this object via a Binding such as
<Border Name="Border">
<ContentPresenter Margin="5,0" Content="{Binding NonUIElement }"/>
</Border>
assuming the DataContext of border is set to an instance of B?
The object will render, but not in the way I think you're hoping for. The Content of the ContentPresenter is set to the instance of A. WPF then tries to figure out how to render this instance of A. It first asks, is this object a UIElement? In this case, the answer is no. So it next looks for a DataTemplate for the type. In this case, there's no DataTemplate for the A class. So it falls back on calling ToString(). So your ContentPresenter will display a TextBlock containing the text "YourNamespace.A".
The fact that A happens to have a member of type ControlTemplate does not affect this logic. To WPF, that's just a chunk of data that A happens to be carrying around. WPF only uses ControlTemplate when there is a Control involved and the ControlTemplate is assigned to the Template property.
So you need either to supply a DataTemplate for A (which of course can access the ControlTemplate and use it to render the instance), or create a named DataTemplate and apply that via ContentPresenter.ContentTemplate, or derive from UIElement instead.
I finally got it with this;
<HierarchicalDataTemplate DataType="{x:Type vm:MapLayerModel}" ItemsSource="{Binding Path=Children, Mode=OneTime}">
**<ContentControl Margin="5" Content="{Binding LayerRepresentation}" Template="{Binding LayerRepresentation.ControlTemplate}" Mode=OneTime/>**
</HierarchicalDataTemplate>
This has been a great personal lesson on WPF templating and its content control model. Thanks again itowlson.

What is the best way in MVVM to build a menu that displays various pages?

I want to build a simple application with the MVVM pattern.
This application will have two main parts:
menu on top
content below
The navigation will be simple:
each menu item (e.g. "Manage Customers" or "View Reports") will fill the content area with a new page that has some particular functionality
I have done this before with code behind where the code-behind event-handler for menu items had all pages loaded and the one that should be displayed was loaded in as a child of a StackPanel. This, however, will not work in MVVM since you don't want to be manually filling a StackPanel but displaying e.g. a "PageItem" object with a DataTemplate, etc.
So those of you who have made a simple click-menu application like this with MVVM, what was your basic application structure? I'm thinking along these lines:
MainView.xaml:
<DockPanel LastChildFill="False">
<Menu
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
where the Menu is filled with a collection of "PageItems" and the DataTemplate displays the Title of each "PageItem object" as the Header of each MenuItem.
And the ContentControl will be filled with a View/ViewModel pair which has full functionality, but am not sure on this.
First, I think you should keep the code-behind event handler, there's no point in changing a simple 2 line event handler to a complex command driven monster for no practical reason (and don't say testebility, this is the main menu, it will be tested every time you run the app).
Now, if you do want to go the pure MVVM route, all you have to do it to make your menu fire a command, first, in some resource section add this style:
<Style x:Key="MenuItemStyle" TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.SwitchViewCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
<Setter Property="CommandParameter"
Value="{Binding}"/>
</Style>
This style will make the menu item fire a the SwitchViewCommand on the attached view model with the MenuItem's DataContext as the command parameter.
The actual view is the same as your code with an additional reference to that style as the ItemContainerStyle (so it applies to the menu item and not the content of the DataTemplate):
<DockPanel LastChildFill="False">
<Menu DockPanel.Dock="Top"
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}"/>
<ContentControl
Content="{Binding SelectedPageItem}"/>
</DockPanel>
Now in the view model you need (I used strings because I don't have your PageItem code):
private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
get { return _selectedViewItem; }
set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }
And use whatever command class you use to make the command call this code:
private void DoSwitchViewCommand(object parameter)
{
SelectedPageItem = (string)parameter;
}
Now, when the user clicks a menu item the menu item will call the SwitchViewCommand with the page item as the parameter.
The command will call the DoSwitchViewCommand that will set the SelectedPageItem property
The property will raise the NotifyPropertyChanged that will make the UI update via data binding.
Or, you can write a 2 line event handler, your choice
i could imagine an ObservableCollection in the VM, that holds all the pages to be callable from the menu.
Then bind an ItemsControl And the ContentControl to it to make the ContentControl always show the CurrentItem from that List.
Of course, the menu will only bind to some Title property
whereas the ContentControl will adopt the whole item and plug in some appropriate view according to the type.
Another option is to use a ListBox instead of a menu, style the ListBox to look like a menu and then you can bind to the selected value, like this:
<DockPanel LastChildFill="False">
<ListBox
ItemsSource="{Binding PageItemsMainMenu}"
ItemTemplate="{StaticResource MainMenuStyle}"
IsSynchronizedWithCurrentItem="True"/>
<ContentControl
Content="{Binding PageItemsMainMenu/}"/>
</DockPanel>
Note the IsSynchronizedWithCurrentItem="True" to set the selected item and the {Binding PageItemsMainMenu/} with the trailing slash to use it.

Render List<Canvas> as list items (or itemscontrol)

I've effectively got a List or a List, XAML that represents the canvas elements... as a return from some stuff I have no control of.
I've been moderately successful rendering via stackpanel.children.add etc, however want to start having the list of canvas within a virtualizing panel or list.
I've set itemssource and datacontext on the <ItemsControl> and set the <DataTemplate> as such
<DataTemplate>
<ContentControl content="{Binding Path=CanvasBody}"/>
</DataTemplate>
This effectively turns entire silverlight body white/blank. I dont really care how I ultimately get to the desired result which is a list of the rendered canvas's... preferably virtualized for speed.
Its a retarded problem, and not ideal as far as how silverlight apps are built, I know...
I'd really appreciate any pointers. THANKS!
Generally to display a list of elements you bind the items control itemssource property to the list and then set a datatemplate for it which displays the desired properties of the list item type. You dont need to set content control. Beyond that I cant see what exactly you are asking.
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ExampleProperty1}"/>
</Grid>
</DataTemplate>
codebehind file:
public class ExampleClass
{
public String ExampleProperty1
{
get
{
return "TEST";
}
}
}
public List<ExampleClass> List {get;} // note that this must be a public PROPERTY,
// not a field!

Resources