WPF Draw UserControl based on the Class of the bound object - c#

im working on a menu which can display items.
Therefor i have a UserControl 'MenuItem' which displays an entity of the type 'MenuItemEntity'.
Cause there are submenus which are displayed in a different way there is another UserControl 'MenuItemGroup' which are bound to an entity of type 'MenuItemGroupEntity' containing different MenuItemEntities.
Now i have the following Problem:
The 'menu' should be bound to an entity of the type 'MenuEntity'.
Inside this i want to have an ObservableCollection which contains MenuItemEntity's AND MenuItemGroupEntity which are displayed in an StackPanel using an ItemsControl.
But i dont know if there is any way to analyze the actual element in the bound collection to draw either an MenuItem or an MenuItemGroup. Something like a switch maybe?
Normaly i would bind the items of the ObservableCollection in the 'MenuEntity' like this:
<ItemsControl ItemsSource="{Binding MenuItemAndGroupCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- IS THERE ANY WAY TO SWITCH BASED ON THE CLASS TYPE? -->
<local:MenuItemGroup DataContext="{Binding}" />
<local:MenuItem DataContext="{Binding}" />
<!-- IS THERE ANY WAY TO SWITCH BASED ON THE CLASS TYPE? -->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I hope someone can help me out of this =(

Simply use "implicit" DataTemplates, like this:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyClass1}">
<local:MyUserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyClass2}">
<local:MyUserControl2/>
</DataTemplate>
<!-- and so on... -->
</Window.Resources>
<ItemsControl ItemsSource="{Binding MenuItemAndGroupCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Do NOT specify an ItemTemplate here -->
</ItemsControl>
</Window>

Related

Is there a way to do what ListViewHeaderItem exactly do?

I want to realize a custom view just like ListView.
Now the problem is I want get a Tapped event from group header, maybe more.
It always creates GroupItem as group view, and can't be change.
I found what ListView do is create ListViewHeaderItem by the system and added to the visual tree when realizing the GroupStyle.HeaderTemplate.
My question is any way to do this?
cs:
public class CustomView : ItemsControl
xaml:
<CollectionViewSource x:Name="itemCVS" IsSourceGrouped="True"
Source="{x:Bind Groups}" ItemsPath="Items"/>
...
<CustomView ItemsSource="{x:Bind itemCVS.View}">
<CustomView.GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<CustomPanel/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</CustomView.GroupStyle>
</CustomView>
CustomView Visual Tree:
CustomView
Windows.UI.Xaml.Controls.Border
Windows.UI.Xaml.Controls.ScrollViewer
Windows.UI.Xaml.Controls.Border
Windows.UI.Xaml.Controls.Grid
Windows.UI.Xaml.Controls.ScrollContentPresenter
Windows.UI.Xaml.Controls.ItemsPresenter
Windows.UI.Xaml.Controls.ContentControl
CustomPanel
Windows.UI.Xaml.Controls.GroupItem
Windows.UI.Xaml.Controls.Grid
Windows.UI.Xaml.Controls.ContentControl
Windows.UI.Xaml.Controls.ContentPresenter
Windows.UI.Xaml.Controls.TextBlock
Windows.UI.Xaml.Controls.ItemsControl
Windows.UI.Xaml.Controls.ItemsPresenter
Windows.UI.Xaml.Controls.ContentControl
CustomPanel
Item 1
Item 2
ListView Visual Tree:
Windows.UI.Xaml.Controls.ListView
Windows.UI.Xaml.Controls.Border
Windows.UI.Xaml.Controls.ScrollViewer
Windows.UI.Xaml.Controls.Border
Windows.UI.Xaml.Controls.Grid
Windows.UI.Xaml.Controls.ScrollContentPresenter
Windows.UI.Xaml.Controls.ItemsPresenter
Windows.UI.Xaml.Controls.ContentControl
Windows.UI.Xaml.Controls.ItemsStackPanel
Windows.UI.Xaml.Controls.ListViewHeaderItem
Windows.UI.Xaml.Controls.ListViewHeaderItem
Windows.UI.Xaml.Controls.ListViewItem 1
Windows.UI.Xaml.Controls.ListViewItem 2
You code has generated the HeaderItem.
Because your CustomerView inherits from ItemsControl, the generated HeaderItem has no style.
So if you want to do this, please manually add to change the style like following.
<CustomerView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<ListViewHeaderItem>
<StackPanel>
<TextBlock Text="{Binding Key}"/>
</StackPanel>
</ListViewHeaderItem>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</CustomerView.GroupStyle>

Itemscontrol with ending control

I'm having a ItemsControl with custom controls wrapped inside. What I want to achieve is to always have a TextBox control along with my custom controls always at the end of WrapPanel.
Here is a slight preview:
Having ItemsControl allows us to have controls of the same type. When TextBoxis added after ItemsControl, it will unfortunately appear on the new line. Therefore I'm forced to somehow append TextBox along with my custom controls in ItemsControl.
<ItemsControl ItemsSource="{Binding MyItems}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<!-- Make items wrap -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Custom control -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CustomControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- This should be inside items control -->
<TextBox />
I did some research and it turns out that I can use DataTemplateSelector class and ItemTemplateSelector which will allow me to choose template for different items.
This is an option of course, however it will require me to insert TextBox into my MyItems (which is ObservableCollection btw.) and I'm not sure if this is the way to go since I will have to locate TextBox and move it at the end of my ObservableCollection, each time a new item is added...
So the question is: what is the proper way to solve this? Looking for experienced developers opinion on this.
You could set the ItemsSource to a CompositeCollection:
<ItemsControl ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ItemsControl.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding MyItems}" />
</ItemsControl.Resources>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource cvs}}" />
<!-- This should be inside items control: -->
<TextBox />
</CompositeCollection>
</ItemsControl.ItemsSource>
<!-- Make items wrap -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Custom control -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CustomControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

An ItemsControl containing UserControl elements

I'm kind of shooting in the dark on this one and have been looking around but couldn't find much of anything related. Pretty much I am trying to create an ItemsControl on a current Window that I've got, so when the user clicks an "Add Product" button on the window, it will add a UserControl to the screen in a horizontal matter.
For starters I am using an MVVM pattern and I have a PricingViewModel which is my ViewModel for the MAIN window. I have a second view model named ComparisonViewModel, which is the ViewModel for the View of the UserControl that I would like to show everytime the user hits the "Add Product" button on the PricingView. Jumping into my code, I've got a declared ObservableCollection and my AddComparison method. The Collection is instantiated in the constructor of the VM.
public ObservableCollection<ComparisonViewModel> Products { get { return _products; } }
public void AddComparison()
{
var products = IoC.Get<ComparisonViewModel>();
Products.Add(products);
}
Next, I've got and ItemsControl in the PricingView that binds to that collection in the PricingViewModel:
<ItemsControl ItemsSource="{Binding Path=Products}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I run it and after hitting add, it just shows the collection name. How can I actually get it to Pop up with a new Comparison User Control when the user hits Add Comparison? Thanks a ton for the help in advance!
You'll want to set the ItemTemplate so the ItemsControl knows how to render each item in the collection (by default, it is just displaying the result of calling .ToString()).
<ItemsControl ItemsSource="{Binding Path=Products}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type namespace:ComparisonViewModel}">
<!-- EXAMPLE -->
<Border BorderBrush="Black"
BorderThickness="2">
<DockPanel Orientation="Horizontal">
<TextBlock Text="{Binding ComparisonResult}"
DockPanel.Dock="Right" />
<TextBlock Text="{Binding Name}"
DockPanel.Dock="Left" />
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have found that I needed to tell the ItemsControl two things... First is what type of "thing" the ItemsControl is:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
And the second is how to display the Control:
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:Widget Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
The final code looks like:
<ItemsControl ItemsSource="{Binding Path=DynamicItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:Widget Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You also need to add a reference to your control namespace in the window declaration stuff:
xmlns:c="clr-namespace:IHateEverything.Controls"

Binding ZIndex from DataTemplate

I have a View that is basically setup like this:
<Grid>
<ViewBox>
<Grid>
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</ViewBox>
</Grid>
The DataTemplate used here can be reduced to this:
<DataTemplate x:Key="AreaItemDisplayDataTemplate">
<Canvas Grid.ZIndex={Binding Z}>
<Grid>
// an shape is displayed here...
</Grid>
</Canvas>
I would now expect the ZIndex to be bound to the Z property of the individual items. When i debug the code, i can also see, that the Z property getter is accessed when i would expect it (whenever i raise the propertychanged event for it, eg) so i assume the binding to work correctly.
However, the ZIndex is not working as expected. The binding to the value has no effect on the actual displayed Z Order. Where am i going wrong with this code?
The content of the DataTemplate gets wrapped in a ContentPresenter so the Canvas in the DataTemplate isn't a direct child of the ItemsPanel Grid. That is the reason the ZIndex property doesn't do anything.
Move the ZIndex Binding to the ItemContainerStyle and it should work.
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.ZIndex" Value="{Binding Z}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

Programmatically reference items rendered by ItemsControl from code behind

I have a Silverlight user control that contains an ItemsControl that renders a StackPanel containing another user control for each item in the data source, the XAML is as follows:
<Grid x:Name="LayoutRoot">
<ItemsControl ItemsSource="{Binding}" x:Name="ValuesItemSource">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="ValuesPanel" Background="Transparent" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:MyCustomControl DataContext="{Binding}" x:Name="Value" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
How would I reference the collection of MyCustomControls (Values) in the code behind of this user control?
(I have an event handler registered in the code behind of this control and I want to invoke a method of each "MyCustomControl" when the event fires)
You need to ask the itemsControl.ItemContainerGenerator for this. See here for an example.

Categories

Resources