WPF Expander overlay used in ItemsControl - c#

I've been searching for some time but all solution I find only tackle parts of my issue.
I'd like to have an ItemsControl with each item containing an Expander. On expanding, the Expander's content should be shown as an overlay overlapping the other items in the ItemsControl and not moving them down.
The following XAML-Code does exactly as supposed to with one big issue: The Expander's content does not overlap the other items but is hidden behind them. I suppose this is due to the ZIndex as the following items in the ItemsControl are added after the Expander's content.
I managed to set one single Expander's ZIndex to 99 using style triggers but this seems to be a too complicated and error-prone solution. Any thoughts?
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<x:Array x:Key="items"
Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
</x:Array>
<DataTemplate x:Key="template">
<Grid Background="Red" Margin="0,0,0,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding}" />
<Canvas Height="25" Grid.Row="1">
<Expander ExpandDirection="Down" Header="Header" Grid.Row="1">
<Expander.Content>
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Expander.Content>
</Expander>
</Canvas>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource items}"
ItemTemplate="{StaticResource template}"
HorizontalContentAlignment="Stretch">
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>

It's going to be tricky to achieve this in the way you are attempting it. The problem is the nested structure -- since each Canvas is nested inside a Grid, you won't be able to control its z-index relative to other Canvas element. To illustrate this, here is a schematic of the visual tree created by your current markup:
<StackPanel> <!-- items panel -->
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
</StackPanel>
With reference to the above, your goal will be to have the Canvas elements appear in front of the siblings of its parent ContentPresenter. This is impossible in this hierarchy, because ZIndex only applies relative to siblings of the same parent element. Now, there might be ways you could massage the above into a flat structure so that you could then apply ZIndex your expanded content as needed.
However, I think an easier and more natural approach would be to use Popup elements for the expanded content. A Popup is a framework primitive that is located outside of the visual tree, and will always sit on top of your other content. You can use a ToggleButton or something similar to create the "expand" effect. For example:
<StackPanel Grid.Row="1">
<ToggleButton x:Name="PopupToggle" Content="Expand" />
<Popup IsOpen="{Binding IsChecked,ElementName=PopupToggle}">
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Popup>
</StackPanel>

Related

Why is my DataGrid going off screen no matter what solution I try?

I am new to C# and WPF trying to get what I should think is a simple thing, but it doesn't work.
I have a data grid being populated by SQL, and no matter what I try, I can't get the Height of the DataGrid to stay within the window. It always just extends down. I want it to be dynamic to the window size.
My very simple code is below, or at least this most recent iteration.
<Page x:Class="TMSMaintenance.PaymentError"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TMSMaintenance"
Title="PaymentError">
<!--<DataGrid Name="MydataGrid" CanUserAddRows="False" SelectionMode="Single" />-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" VerticalAlignment="Stretch" >
<DataGrid x:Name="MydataGrid"
VerticalAlignment="Stretch"
MinHeight="100"
VerticalScrollBarVisibility="Auto" VerticalContentAlignment="Stretch">
</DataGrid>
</DockPanel>
</Grid>
</Page>
I have tried wrapping in a ScrollView - it didn't work. I tried setting the Height by binding it to the window - it didn't work. I have tried the Grid.RowDefinition Height = "*" and "1*" - it didn't work. VerticalAlignment = "Stretch" also didn't work.
So what am I missing?
Edit: Maybe I should also say that this is on a Page file called within a frame tag. Not sure if it makes a difference here.
<StackPanel CanVerticallyScroll="True" CanHorizontallyScroll="True">
<!-- Navigation -->
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Files Lookup" Margin="0,0,10,0"/>
<Button Content="Payment Error" Margin="0,0,10,0"/>
<Button Content="Carrier Maintenance" Margin="0,0,10,0"/>
<Button Content="Payment File" Margin="0,0,10,0" />
</StackPanel>
<ScrollViewer>
<Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" ></Frame>
</ScrollViewer>
</StackPanel>
Get rid of that DockPanel. Grid is a much more flexible container for controls and you don't need to pack container into container at all. This alone should do the trick.
Good practice is to not use DockPanels at all. Never. Everything you can achieve with DockPanels can be achieved with Grids (with a bit more of coding, but it gives you more flexible solution and better maintainability of your code).
Also get rid of VerticalContentAlignment (not needed in case you described) and you don't need to define VerticalAlignment (nor HorizontalAlignment) to Stretch, since it's a default value of that property.
EDIT:
I haven't noticed the second sample of your code. Everything I wrote before still applies and will make your code better, but I think your problem is with nesting your Page in your main container (Window or whatever it is).
Try replacing:
<StackPanel CanVerticallyScroll="True" CanHorizontallyScroll="True">
<!-- Navigation -->
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<Button Content="Files Lookup" Margin="0,0,10,0"/>
<Button Content="Payment Error" Margin="0,0,10,0"/>
<Button Content="Carrier Maintenance" Margin="0,0,10,0"/>
<Button Content="Payment File" Margin="0,0,10,0" />
</StackPanel>
<ScrollViewer>
<Frame x:Name="MainFrame" NavigationUIVisibility="Hidden" ></Frame>
</ScrollViewer>
</StackPanel>
To:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal"
Margin="0,10,0,0">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,10,0" />
</Style>
</StackPanel.Resources>
<Button Content="Files Lookup"/>
<Button Content="Payment Error"/>
<Button Content="Carrier Maintenance"/>
<Button Content="Payment File"/>
</StackPanel>
<Frame x:Name="MainFrame"
Grid.Row="1"
NavigationUIVisibility="Hidden" />
</Grid>
I have also simplified your styling on Buttons and I'd recommend you to change your Page to UserControl.

WPF UserControl not rendering. UserControl is instantiated using DataTemplates

I have a user control that I`m trying to show via data templates. Unfortunately, the control never shows. Here's the DataTemplate for it:
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
If I put regular controls inside those DataTemplate tags, I can see them. My SpecialControl however, won't show. Here's the xaml file for my SpecialControl:
<UserControl x:Class="CustomControls.SpecialControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<StackPanel>
<CheckBox Content="Hello World" />
<Button >
<TextBlock Text="Goodbye world"/>
</Button>
</StackPanel>
</UserControl>
For some reason, this control is invisible at run time. If I put them directly into the template, I'll see them. I know I could just do that, but I want to do something more complex with this class, using databinding, and custom behavior. I tried using Snoop, and I can see my SpecialControl in there somewhere, with a ContentPresenter that's not expandable: no sign of The checkbox or the button.
Edit:
Here is the View that is using the SpecialControl: I left out many of the templates I'm using, because I don't want this to be too crowded.
<UserControl x:Class="Tools.EditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:ctrls="clr-namespace:CustomControls"
xmlns:local="clr-namespace:Tools"
mc:Ignorable="d"
x:Name="This">
<UserControl.Resources>
<!--More templates -->
<DataTemplate x:Key="groupBoxTemplate" >
<ctrls:SpecialGroupBox Header="{Binding Path=Title}" Margin="2" DockPanel.Dock="Top">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector="{DynamicResource guiTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
<!-- Set up the width of the individual items based on how many columns we are supposed to have and what the adjusted width of the wrapPanel is. This way the 4th item will be placed on the 2nd row if the ColumnCount is 3. -->
<WrapPanel.ItemWidth>
<MultiBinding Converter="{StaticResource itemWidthConverter}">
<Binding Path="ColumnCount"/>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</WrapPanel.ItemWidth>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ctrls:SpecialGroupBox>
</DataTemplate>
<DataTemplate x:Key="listViewTemplate">
<ctrls:SpecialControl/>
</DataTemplate>
<local:GuiTemplateSelector
... <!--More templates -->
GroupBoxTemplate="{StaticResource groupBoxTemplate}"
SpecialTemplate="{StaticResource listViewTemplate}"
x:Key="guiTemplateSelector"/>
</UserControl.Resources>
<DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel Name="rootPanel" DockPanel.Dock ="Top" Width="Auto" Height="Auto">
<ItemsControl ItemsSource="{Binding guiItemsList}" ItemTemplateSelector=" {StaticResource guiTemplateSelector}">
</ItemsControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>
If you're wondering what the SpecialGroupBox does, it holds other controls, positioning them a certain way. There are a few of them in this window, and they work. It is inside of one of these SpecialGroupBoxes that my SpecialControl is supposed to appear.
Does your UserControl have a corresponding .xaml.cs file? This problem seems to happen when it doesn't.
If you're using Visual Studio, try copying all the XAML from your UserControl, then deleting it from your project, adding a new UserControl (with the same name as before), then pasting your content back into the XAML file. This will ensure that you have the correct .xaml.cs file set up.
See this answer.
In your post you didn't mention how you are using the listViewTemplate . if your using inside listview/listbox you can try like this it will load the user control:
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate >
<ContentControl ContentTemplate="{StaticResource listviewDataTemplate}" Content="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

ContentPresenter in UserControl

I'm new to WPF and I'm trying to create an UserControl which will have some nested content.
<my:InformationBox Header="General Information" Width="280">
<StackPanel>
<Label>Label1</Label>
<Label>Label2</Label>
</StackPanel>
</my:InformationBox>
As you can see I want to put a StackPanel into it. As I read some articles I am supposed to add ContentPresenter to my UserControl, so I did, but I cannot find what should be binded to it's Content property.
Here is my UserControl code
<UserControl x:Class="ITMAN.InformationBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="280" Name="infoBox" Loaded="infoBox_Loaded">
<StackPanel Width="{Binding ElementName=infoBox, Path=Width}" HorizontalAlignment="Stretch">
<Label Content="{Binding ElementName=infoBox, Path=Header}" />
<Border BorderThickness="0,1,0,0" Padding="10 5" Margin="5 0 5 10" BorderBrush="#B4CEDE">
<StackPanel>
<ContentPresenter Content="{Binding Content}" />
<Label Content="End" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>
I've tried many combinations from various articles, but I cannot find any working example of what I want to achieve.
Similiar question was asked earlier by another user, but given there answers didn't help me: Does anyone have a simple example of a UserControl with a single ContentPresenter?
ContentPresenter is kind of a magic control. If you don't supply anything to it, it will automatically set the Content, ContentTemplate and ContentTemplateSelector property with a TemplateBinding to the TemplatedParent. Which means, you don't need to supply anything to it, just
<ContentPresenter/>
in your UserControl, and it should automatically use the corresponding properties found in your UserControl.
Also remember that a binding like {Binding Content} always referes to your DataContext, which i guess is not what you wanted.
I solved this problem by applaying custom style to GroupBox. I've created Syle in ResourceDictionary, which looks as follows
<Style x:Key="InformationBoxStyle" TargetType="GroupBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label>
<ContentPresenter Margin="4" ContentSource="Header"
RecognizesAccessKey="True" />
</Label>
<Border Grid.Row="1" BorderThickness="0,1,0,0" Padding="10 5"
Margin="5 0 5 10" BorderBrush="#B4CEDE">
<StackPanel>
<ContentPresenter />
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And applied this style to GroupBox
<GroupBox Header="General Information" Width="280" Style="{StaticResource InformationBoxStyle}">
<StackPanel>
<Label>Label1</Label>
<Label>Label2</Label>
</StackPanel>
</GroupBox>
This code works as expected
You may also refer to this great article, which shows different options to achieve it:
http://www.codeproject.com/Articles/82464/How-to-Embed-Arbitrary-Content-in-a-WPF-Control
It also describes why ContentPresenter doesn't work in my code.
You need to create a dependency property on your UserControl code behind such as InnerContent.
public object InnerContent
{
get { return GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(object), typeof(ConfirmationControl), new PropertyMetadata(null));
Then you need to bind to that InnerContent on the XAML side of that UserControl.
<ContentControl Content="{Binding InnerContent, ElementName=userControl}" />
Then when you use it instead of placing content in the UserControl directly and overwriting the existing content just add it to the InnerContent portion.
<UserControls:InformationBox>
<UserControls:InformationBox.InnerContent>
<TextBlock Text="I'm in the InnerContent" />
</UserControls:InformationBox.InnerContent>
</UserControls:InformationBox>
Otherwise using a Template or Style is just as good but if you're wanting to package up a UserControl for use without forcing anyone to also reference a style or template this is probably one of your better options.

WPF Issues with Control Layout

I am making an application that connects to our billing software using its API, and I am running into a few issues getting the layout working properly.
I want to make it so that when one of the expanders is minimized, the other window fills the gap, and when it is expanded again the other expander goes back to where it was. Right now when the arrow is clicked on one, there is just an empty gap. I used a DockPanel as the parent which I assumed would automatically do this, but it isn't working.
Second question, is there a way to make these areas resizable? I don't want to try and get too frisky with allowing the user to undock the menus (don't even know if that is possible with just straight WPF) but it would be nice if they could change the width/height of them.
Also, just a newbie question to C#, but what is the equivalent of a C++ header file? It looks like you just use .cs files, but I am not sure. I want to extract all of my functions that pull the data from the billing software and put them into a different file to clean up the code.
Here is my XAML...
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Billing Management" Height="550" Width="754" xmlns:shared="http://schemas.actiprosoftware.com/winfx/xaml/shared" WindowStartupLocation="CenterScreen" WindowStyle="ThreeDBorderWindow">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="22" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu Height="22" Name="menu1" Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Top" HorizontalContentAlignment="Left" IsEnabled="True" IsMainMenu="True">
<MenuItem Header="_File">
<MenuItem Header="_Open" />
<MenuItem Header="_Close" />
<Separator/>
<MenuItem Header="_Exit" />
</MenuItem>
</Menu>
<TabControl Name="tabControl1" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" BorderThickness="1" Padding="0" TabStripPlacement="Bottom" UseLayoutRounding="False" FlowDirection="LeftToRight" Grid.Row="1">
<TabItem Header="Main" Name="tabItem1" Margin="0">
<DockPanel Name="dockPanel1" LastChildFill="True">
<ListBox Height="100" Name="listBox3" DockPanel.Dock="Top" />
<ListBox Name="listBox4" Width="200" DockPanel.Dock="Right" />
<DockPanel Height="Auto" Name="dockPanel2" Width="Auto" VerticalAlignment="Stretch" LastChildFill="True">
<shared:AnimatedExpander Header="Staff Online" Width="200" Name="expanderStaffOnline" IsExpanded="True" Height="194" BorderThickness="0" DockPanel.Dock="Top" VerticalContentAlignment="Stretch">
<ListBox Name="listboxStaffOnline" Width="Auto" Height="Auto" Margin="0" VerticalAlignment="Stretch" Loaded="listboxStaffOnline_Loaded" />
</shared:AnimatedExpander>
<shared:AnimatedExpander Header="Test Menu 2" Height="Auto" Name="animatedExpander1" BorderThickness="1" Margin="0,0,0,0" IsExpanded="True" VerticalContentAlignment="Stretch">
<ListBox Height="Auto" HorizontalAlignment="Stretch" Name="listBox6" VerticalAlignment="Stretch" Margin="0" BorderThickness="1" />
</shared:AnimatedExpander>
</DockPanel>
<ListBox Height="100" Name="listboxAdminLogs" DockPanel.Dock="Bottom" Loaded="listboxAdminLogs_Loaded" />
<ListBox Name="listBox5" />
</DockPanel>
</TabItem>
<TabItem Header="Support" Name="tabItem2" Margin="0">
</TabItem>
<TabItem Header="Clients" />
<TabItem Header="Billing" />
<TabItem Header="Orders" />
</TabControl>
</Grid>
</Window>
You should place your expanders in a Grid which will allow the content to expand and fill automatically. providing the Row / Column definitions width / height are set to Auto or *.
If you want to resize you will need to use the GridSplitter; see http://www.wpftutorial.net/GridLayout.html for further details.
I don't know C++ so can't really tell you want the equivalent of header files are but I think what you are trying to do is achievable using partial classes; see http://msdn.microsoft.com/en-us/library/wa80x488(v=vs.80).aspx for further details.
And finally, to answer your question in your comments; To get a uniform appearance for all controls in WPF you would need to create custom Styles which use a common set of Colors / Brushes.
I suggest you put the expanders in a Grid and set the row height of the gridrows to auto.
To make it resizable simply add GridSplitters in inbetween rows of this grid.
As for your added question: I think it would be better to copy the style of the expanders' headers and use it for a label or other header.
Also (many questions in this one): C# has no header files.

How to have a control fill all available space

I have a xaml code:
<Grid>
<WrapPanel>
<TextBox ></TextBox>
<Button Content="GetIt" />
</WrapPanel>
</Grid>
How i can to get all available space for textBox?
i want to do something like that:
|[____________________][GetIt]|
There are a number of ways this can be achieved, including this one:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox />
<Button Grid.Column="1">GetIt</Button>
</Grid>
Try this:
<Grid>
<TextBox HorizontalAlignment="Stretch" Margin="2,2,102,2"></TextBox>
<Button HorizontalAlignment="Right" Width="100" Content="GetIt" />
</Grid>
Just make the button the desired width and the text box will fill up the rest.
Thanks for the catch; corrected above to properly handle margin on right. This does, however, require you to update the margin when the button width changes. Two columns is a better solution if you plan to change the spacing often. Using the margin is cleaner if you have several controls in the grid and don't want to create nested grids to handle this kind of split.
The simplest way is to use a DockPanel instead of a Grid (the default for LastChildFill is true but I also added it here for clarity):
<DockPanel LastChildFill="True">
<Button Content="GetIt" DockPanel.Dock="Right" />
<TextBox ></TextBox>
</DockPanel>
Here's a way to achieve the layout that you're looking for:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="2"/>
</Style>
</Page.Resources>
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<!-- Because the Button is fixed in size, you can divide the row it's
in using a DockPanel: the Button is docked to the right edge, and the
TextBox fills up the remaining available space. -->
<Button Margin="2" Padding="2" DockPanel.Dock="Right">GetIt</Button>
<TextBox />
</DockPanel>
<!-- Because the TextBoxes *aren't* fixed in size, you can't use docking,
as it won't size them. So put them in a Grid and use star sizing to
divide the grid's vertical space into two equal parts. The Grid will
fill up the remainder of the (outer) DockPanel. -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0">Another TextBox</TextBox>
<TextBox Grid.Row="1">Yet another TextBox</TextBox>
</Grid>
</DockPanel>
</Page>

Categories

Resources