Custom ListViewItem with embedded Expander - wpf

I'm creating a simple file browser where it's display will mimic that of the Windows (7) Explorer "Content" display mode. I envision a ListView control whose ItemsSource will be bound to a collection of FileSystemInfo objects. Each of the ListViewItems will show filename, last modified date/time, etc. However, I also want them to have an Expander which, when a user clicks it, will expand vertically to show the file's contents.
Below is my XAML code that I created with Expression Blend. This is the general template of what I want the ListViewItem to be. However, the Expander won't cause the Border's vertical height to expand out to match the additional space required by the Expander's Grid content (in this example a ScrollViewer that has a height of 500 -- obviously each file may have different dimensions depending on content -- image, plain text, etc).
So, what am I doing wrong here? Furthermore, I'm wondering if the ListView control will even behave properly by having its Item elements vertically expand/contract.
This is the ControlTemplate I wish to use for the ListItemView:
<Border BorderBrush="Black" BorderThickness="1" Background="Beige">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.25*"/>
<ColumnDefinition Width="0.50*"/>
<ColumnDefinition Width="0.25*"/>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Left" TextWrapping="Wrap" Text="FileName" d:LayoutOverrides="Height" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Text="FileType" d:LayoutOverrides="Width, Height" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" TextWrapping="Wrap" Text="DateTime Last Modified" VerticalAlignment="Center" d:LayoutOverrides="Width" Grid.RowSpan="2" HorizontalAlignment="Center"/>
<Expander Header="Expander" d:LayoutOverrides="Width" Grid.ColumnSpan="3" Grid.Row="2">
<Grid>
<ScrollViewer Content="File Contents" Height="500"/>
</Grid>
</Expander>
</Grid>
</Border>

Related

How to position WPF nested controls absolutely in a window

I have some controls nested in some panels.
The panels themselves are not arranged with the same x position within a window.
I'd like the controls inside the panels to have a fixed position relative to the window, not the panel (so they all line up vertically, in this case).
To be clear, the Panels themselves have an arbitrary position in the window. One can imagine that the user should be able to effect the Panel positions, but the nested controls would stick to the same X coordinate relative to the window.
I don't think I can use a Canvas, because that's positioning relative to the edges of the canvas.
Any ideas?
Many thanks.
Don't use Panels... instead, use Grids. If you use Grids, then you can take advantage of the Grid.IsSharedSizeScope Attached Property and the ColumnDefinition.SharedSizeGroup Property. Using these, you can line up columns from different Grids, as long as you have a parent Grid with the IsSharedSizeScope property set to true.
Here is an example taken from the first linked page on MSDN:
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Grid ShowGridLines="True" Margin="0,0,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="FirstColumn"/>
<ColumnDefinition SharedSizeGroup="SecondColumn"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="FirstRow"/>
</Grid.RowDefinitions>
<Rectangle Fill="Silver" Grid.Column="0" Grid.Row="0" Width="200" Height="100"/>
<Rectangle Fill="Blue" Grid.Column="1" Grid.Row="0" Width="150" Height="100"/>
<TextBlock Grid.Column="0" Grid.Row="0" FontWeight="Bold">First Column</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" FontWeight="Bold">Second Column</TextBlock>
</Grid>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="FirstColumn"/>
<ColumnDefinition SharedSizeGroup="SecondColumn"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="FirstRow"/>
</Grid.RowDefinitions>
<Rectangle Fill="Silver" Grid.Column="0" Grid.Row="0"/>
<Rectangle Fill="Blue" Grid.Column="1" Grid.Row="0"/>
<TextBlock Grid.Column="0" Grid.Row="0" FontWeight="Bold">First Column</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" FontWeight="Bold">Second Column</TextBlock>
</Grid>
</StackPanel>

Border wrapping wrong element in WPF

I'm trying to add a border to some controls in XAML - the problem is, whenever I apply wrapping a certain element it wraps the whole window, probably on the first grid element?
This also happens when I try to use it around the WebBrowser. Any suggestions?
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RAT-t00l" Height="850" Width="700">
<Grid x:Name="BigGrid" HorizontalAlignment="Right" Width="682">
<TextBox Name="txt_Log" HorizontalAlignment="Left" Height="657" Margin="4,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="417" IsReadOnly="True"/>
<Button Name="btn" Click="btn_Connect_Click" Background="LightGreen" Content="Connect" HorizontalAlignment="Left" Margin="266,680,0,0" VerticalAlignment="Top" Width="75"/>
<Button Name="btn_disc" Click="btn_Disconnect_Click" Background="Pink" Content="Disconnect" HorizontalAlignment="Left" Margin="346,680,0,0" VerticalAlignment="Top" Width="75"/>
<Button Name="btn_fetch" Click="btn_Fetch_Click" Content="Fetch data" HorizontalAlignment="Left" Margin="266,707,0,0" VerticalAlignment="Top" Width="155" Height="24"/>
<Button Name="btn_eraseLog" Click="btn_EraseLog_Click" Content="Erase log from target" HorizontalAlignment="Left" Margin="266,736,0,0" VerticalAlignment="Top" Width="155" Height="25"/>
<WebBrowser
Name="map"
HorizontalAlignment="Left"
Height="347"
Margin="426,10,0,0"
VerticalAlignment="Top"
Width="246"
LoadCompleted="wb_LoadCompleted"
/>
Here's the border. Meaning to wrap only the grid inside.
<Border Name="mask" CornerRadius="20" Height="auto" Width="auto" BorderThickness="1" BorderBrush="Black">
<Grid x:Name ="GeneralGrid" Margin="426,362,10,291" ShowGridLines="True" Background="LightGray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="61*" ></ColumnDefinition>
<ColumnDefinition Width="185*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="IP">IP</TextBlock>
<TextBlock Name="ISP" Grid.Row="1">ISP</TextBlock>
<TextBlock Name="Location" Grid.Row="2">Location</TextBlock>
<TextBlock Name="Longitude" Grid.Row="3">Longitude</TextBlock>
<TextBlock Name="Latitude" Grid.Row="4">Latitude</TextBlock>
</Grid>
</Border>
</Grid>
</Window>
I would recommend that you do NOT continue to use the Visual Studio Designer as you have been. It does a very poor job of creating the XAML that we actually want. For example, your UI elements have all got an exact Margin set on them (thanks to the VS Designer I imagine) and this can make things awkward for you later on.
WPF was really designed to enable developer to use resizable controls so that the UI can resize itself when the user resizes the application. Different Panels provide different sizing abilities to their child controls and you can find out more about that from the Panels Overview page on MSDN. However, back to your question regarding the Grid class.
Because you have used the Visual Studio Designer, your controls have not ended up in the Grid cells that you wanted, instead just being placed 'on top of', or 'in front of' them. In order to place a control into a particular Grid cell, you need to set the Grid.Row and/or Grid.Column Attached Properties. See this example taken from the last linked page from MSDN:
<Grid VerticalAlignment="Top" HorizontalAlignment="Left" ShowGridLines="True" Width="250" Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock FontSize="20" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="0">2005 Products Shipped</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="0">Quarter 1</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="1">Quarter 2</TextBlock>
<TextBlock FontSize="12" FontWeight="Bold" Grid.Row="1" Grid.Column="2">Quarter 3</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0">50000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1">100000</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2">150000</TextBlock>
<TextBlock FontSize="16" FontWeight="Bold" Grid.ColumnSpan="3" Grid.Row="3">Total Units: 300000</TextBlock>
</Grid>
You appear to have placed all your BigGrid elements into a single cell of that layout grid, and the only thing stopping them from appearing on top of each other is the fact you've defined margins. You have not defined a margin for your border, but then defined margins for its children, which means they'll be offset.
Really, for best layout, you want to avoid margins as much as possible and divide your BigGrid into rows and columns. Then place your UI into those cells. If your border is within its own cell you will not have it appear to wrap everything.

WPF: avoid redrawing of groupbox to fit content

I'm using a page with a grid with 3 rows where every row contains a groupbox. The groupboxes also contain different grids with different content.
I would like to have all of my groupboxes to fit the same width. Initially that's the case. But when the content of the first group box is updated from code behind, the group box gets redrawn for a very short moment (width is smaller), but a second later it again fits to the width.
Initially it is: "not connected" which changes to "connected", so width is smaller.
The Width of my window is set to maximize, I don't want to set fix width and height for the page and the grids. Is there a way I can only update the content, without the box trying to fit to the content?
Example ( 2 rows):
<Grid Grid.IsSharedSizeScope="True" Name="globalGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="globalCol"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="RowA"/>
<RowDefinition SharedSizeGroup="RowA"/>
<RowDefinition SharedSizeGroup="RowA"/>
</Grid.RowDefinitions>
<GroupBox Header="Connection status"
Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Name="Connected"
Style="{StaticResource iconConnectStyle}"
DataContext="{Binding DeviceConnection}"
Grid.Row="0" Grid.Column="0" />
<TextBlock Text="device 1:"
Grid.Row="0" Grid.Column="1"
Style="{StaticResource statusText}"/>
<TextBlock Name="dev1" Grid.Row="0" Grid.Column="2"
Style="{StaticResource statusText}" />
</Grid>
</GroupBox>
<GroupBox Header="processing status"
Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Stretch">
<StackPanel>
<TextBlock x:Name="processState" Style="{StaticResource statusText}"/>
<ProgressBar x:Name="progressState"/>
</StackPanel>
</GroupBox>
</Grid>
I'm now using a UniformGrid, that solved the problem...

TextBlock Wrapping Property Doesn't Work

In the following XAML, I am trying to wrap the TextBlock which binds to "PortfolioCodes" and "CommentaryText" but it seems that "Wrapping" doesn't work for TextBlock. I tried every possible suggestion I could find on this web site but all in vain. Can someone please help.
<Grid>
<ListBox ItemsSource="{Binding Path=Summaries}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="5" BorderBrush="LightGray" BorderThickness="1" Padding="4" Margin="4">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="15"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0">No Of Security</TextBlock>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding Path=PortfolioSecurityCount}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1">Portfolio Code(s)</TextBlock>
<Grid Grid.Column="2" Grid.Row="1" >
<TextBlock TextWrapping="Wrap" Text="{Binding Path=PortfolioCodes}"></TextBlock>
</Grid>
<TextBlock Grid.Column="0" Grid.Row="2">Commentary Text</TextBlock>
<Grid Grid.Column="2" Grid.Row="2" >
<TextBlock Grid.Column="2" Grid.Row="2" TextWrapping="Wrap" Text="{Binding Path=CommentaryText}"></TextBlock>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Based on Guge response, I have changed xaml as below and now its working.
<Grid x:Name="LayoutRoot">
<ListBox x:Name="SummaryListBox" ItemsSource="{Binding Path=Summaries}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="5" BorderBrush="LightGray" BorderThickness="1" Padding="4" Margin="4">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="15"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0">No Of Security</TextBlock>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding Path=PortfolioSecurityCount}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1">Portfolio Code(s)</TextBlock>
<TextBlock Grid.Column="2" Grid.Row="1" TextWrapping="Wrap" Text="{Binding Path=PortfolioCodes}" ></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2">Commentary Text</TextBlock>
<TextBlock Grid.Column="2" Grid.Row="2" TextWrapping="Wrap" Text="{Binding Path=CommentaryText}"></TextBlock>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Change the width of your third ColumnDefinition from "Auto" to "*", that way it only takes up whatever is left of your horizontal space.
To try to explain this:
Screen area in WPF is distributed in a two pass algorithm. First each visual element asks each child how much space it needs, with an indication of how much is available. These children do the same for their children.
Then each visual element tells each child how much they are actually going to get. These children, again, do the same for their children.
Your code failed to do what you wanted because the Grid in the DataTemplate told its third column children they could have all the horizontal space they wanted ("auto") in the first run. Those textboxes then thought that they wouldn't have to wrap. So they just reported back their desired width, and one line worth of height.
In the second run the Grid found that "auto" turned out to be a little less than what those children wanted. And the Grid still only gave them one line worth of height, so wrapping was out of the question. The children then had no other option left to them but to truncate the text.
When the third column width is set to "*", the grid will tell the children in the that column exactly how many horizontal pixels are left after the first column got their "auto" and the second column got their 15. Now the textboxes can figure out that they may want to wrap, and they report back "Ok, Dad, I'll make do with those measly horizontal pixels, but at least give me what I want in verticals". There is no limit to the vertical space, so they get what they need to present all their glorious content.
Try to give width to your TextBlock, by default TextBlock takes all available space and doesn't wrap text

How to layout controls in the top right hand corner in XAML/Silverlight?

I am going through the process of designing my first Silverlight application based upon the PivotViewer control from Silverlight 4. I am having problems organizing the bar at the top as per my design:
(source: richard-slater.co.uk)
I have found ways of left aligning the Logo and Title, a way of right aligning the buttons with various combinations of panels however there are two major problems with it.
The XAML looks really really ugly, nesting panels seems to work but doesn't seem like good practice.
I can't seem to find a way of handling resizing the window down without either clipping or overlapping.
I have acheived the best results with the following code:
<StackPanel x:Name="LayoutHeader" Margin="4" Height="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal">
<Image x:Name="LogoImage" Height="50" Width="50" Source="/EVEMonPivot;component/EVEMonLogoBlue.png" Grid.Column="0" Grid.Row="0" />
<TextBlock x:Name="TitleText" Height="50" Text="EVEMon Pivot" FontSize="40" Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" FontWeight="Bold" Padding="10,0,0,0" />
</StackPanel>
<StackPanel x:Name="NavHeader" Margin="4" Height="50" Grid.Column="0" Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="StackExButton" Style="{StaticResource NavButton}" Click="StackExButton_Click">EVE Online StackExchange</Button>
<Button x:Name="BugsButton" Style="{StaticResource NavButton}">Bugs & Suggestions</Button>
</StackPanel>
I intend to move some of the properties into styles, however it still feels messy.
The above code can also result in the following in small windows:
(source: richard-slater.co.uk)
Is there a better way?
If you don't like nesting panels, a Grid might be a better option. With your four elements, have a five column grid like this:
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image x:Name="LogoImage"
Height="50"
Width="50"
Source="/EVEMonPivot;component/EVEMonLogoBlue.png"
Grid.Column="0" />
<TextBlock x:Name="TitleText"
Height="50"
Text="EVEMon Pivot"
FontSize="40"
Grid.Column="1"
Grid.Row="0"
VerticalAlignment="Center"
FontWeight="Bold"
Padding="10,0,0,0" />
<Button x:Name="StackExButton"
Grid.Column="4"
Style="{StaticResource NavButton}"
Click="StackExButton_Click">EVE Online StackExchange</Button>
<Button x:Name="BugsButton"
Grid.Column="5"
Style="{StaticResource NavButton}">Bugs & Suggestions</Button>
</Grid>
This sets four columns to Auto-size, so they adjust to the size of your UI elements, and the centre column is Star-sized so it fills the rest of the space between them.
While you can use a star-sized grid column to enforce a collapsible region between the controls, you're still left to account for what happens when there simply isn't enough room (eg. 600 pixels of display in a 400-pixel wide area.) What I think you need is a ScrollViewer, which is a ContentControl that lets you determine when scroll bars appear.
In the markup below I am doing 2 things: First, I am using the Silverlight toolkit's DockPanel to isolate the left and right sections of the display (a very similar thing can be accomplished with a 3-column Grid with Cols 0 and 2 set to "Auto" and Col 1 set to "*", but the specific use of Left and Right in the DockPanel may make the intent more readable.) Second, the whole thing is being wrapped in a ScrollViewer with the HorizontalScrollBarVisibility set to "Auto" - when the contents is too big to fit, put up a scrollbar.
<UserControl xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit" x:Class="SilverlightApplication2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<toolkit:DockPanel >
<StackPanel toolkit:DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Top" Height="50" Margin="5">
<TextBlock Text="Some long text" FontSize="30"/>
</StackPanel>
<StackPanel toolkit:DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right" Height="50" Margin="5">
<Button Content="First Button" Margin="5"/>
<Button Content="Second Button" Margin="5"/>
</StackPanel>
</toolkit:DockPanel>
</ScrollViewer>
<TextBlock Grid.Row="1" Text="Body Content (DataGrid, etc.)" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>

Resources