XAML: Placing controls over an Image
I need to display an Image
in a Grid
cell. On top of the image, a StackPanel
of controls (such as zoom, brightness etc) has to be added at the bottom right corner of the image. How can this be done. I am doing the following, but not sure how to position the StackPanel
of controls on the lower right corner of the im开发者_JS百科age. The position needs to be maintained even if user resizes browser window.
<Grid Grid.Column="1" Height="387" HorizontalAlignment="Left" Name="Image_Border" VerticalAlignment="Top" Width="799">
<Border BorderBrush="Black" BorderThickness="1" Grid.ColumnSpan="2" Height="Auto" HorizontalAlignment="Left" Name="Border_Image" VerticalAlignment="Top" Width="Auto" >
<Canvas Height="Auto" HorizontalAlignment="Left" Name="ImageCanvas" VerticalAlignment="Top" Background="Transparent" Width="Auto">
<Image Canvas.Left="0" Canvas.Top="0" Height="Auto" Name="imageName" Stretch="None" Width="Auto" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Canvas>
</Border>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Right">
<!--Stackpanel of controls to be placed at the lower right corner image -->
</StackPanel>
</Grid>
The intent of the UI is a little unclear so I'll hold back from giving a more complete answer for now. However if you simply had an image and you want to overlay the bottom right of this image with controls then a Grid
is the solution:-
<Grid>
<Image x:Name="img" Stretch="None" />
<StackPanel x:Name="control" VerticalAlignment="Bottom" HorizontalAlignment="Right">
<!-- controls here -->
</StackPanel>
</Grid>
This grid will size to whatever size the Image is (unless its smaller the the controls panel), the controls panel will float on top in the bottom right corner of the image. This is case of less is more let the components do the work.
Since one of your controls is "Zoom" I suspect you will have other issues to solve which might ultimately make this problem moot but the above is the essence of what you need for now.
I solved a similar problem in WPF by overriding
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
I needed to shuffle things around as the user enlarged the screen, so I could resize an image up maintaining aspect ratio and to make room for 3x and 4x zoom had to move controls around and then overlay some on the image.
I achieved this by having an outermost Canvas element with nested inside it a couple of Grids to handle most of the layout and the sets of controls that needed moving around or overlaying in StackPanels.
Note how I tried to avoid too much hardcoding by driving the logic from the initial layout - if you rearrange things a bit in the XAML it should still cope by sizing things relative to a couple of key objects.
/**
* Stores sizes used by OnRenderSizeChanged() to measure relative changes, based off the size of elements initially drawn.
*
* Everything is driven by the size and position of the grid rightSideControls because that is immediately
* adjacent to the visible bitmap firebar when we open.
*
* SEImagesBitmap is drawn in the background so its size can actually be way too big
*
*/
private void InitSizesOnceConstructed()
{
if (gotSizes)
{
return;
}
gotSizes = true;
initialBitmapSize.Height = SEImagesBitmap.ActualHeight;
initialBitmapSize.Width = SEImagesBitmap.ActualWidth;
initialRightSideControlsBounds = new Rect(
Canvas.GetLeft(rightSideControls),
0,
rightSideControls.ActualWidth,
rightSideControls.ActualHeight);
initialWindowExtra.Width = Width - (initialRightSideControlsBounds.Right + 4);
initialWindowExtra.Height = Height - (Canvas.GetTop(bottomControls) + bottomControls.ActualHeight);
}
/**
* Moves things around to fit once the window is big enough for the main image to rescale, starting from trying to fit at scale 1 and moving up.
*
* Relies heavily on SizeAtScale() to decide if that scale will fit, but the actual layout is done here.
*
* May move controls on top of the image, so changes text color to white to make it visible on the image typical black margin.
*
* Standard event invoked after the size is changed for any reason.
* @warning if you change the layout logic in here must change SizeAtScale() to match!
*/
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
if (gotSizes)
{
if (lastResizePutControlsOverImage)
{ // cleanup color changes because we might not put it back
lastResizePutControlsOverImage = false;
TEXT_Mask.Foreground = Brushes.Black;
}
Size newSize = sizeInfo.NewSize;
// find the largest scale that will fit, regardless of whether we just resized up or down
int scale = 1;
Size minSizeForScale = new Size(0, 0); // fake init because doesn't like not having one, but always at least once through loop below
for (; scale <= MAX_IMAGE_SCALE; scale++)
{
minSizeForScale = SizeAtScale(scale);
if (newSize.Height < minSizeForScale.Height ||
newSize.Width < minSizeForScale.Width)
{
scale = Math.Max(1, scale - 1);
break;
}
}
if (drawingCanvas.Scale != scale || justForcedMaximize)
{
justForcedMaximize = false;
bool putBottomControlsOnImage = false;
if (newSize.Height > 1024)
{
// do our best to fit on to a 1050 screen
if (WindowState == WindowState.Maximized && scale == 3)
{
scale = 4;
}
putBottomControlsOnImage = true;
}
drawingCanvas.SetScale(scale);
drawingCanvas.Width = scale * 256;
drawingCanvas.Height = scale * 256;
SEImagesBitmap.Width = scale * initialBitmapSize.Width;
SEImagesBitmap.Height = scale * initialBitmapSize.Height;
Canvas.SetLeft(fireLegendGrid, SEImagesBitmap.Width);
fireLegendGrid.Height = SEImagesBitmap.Height;
Canvas.SetLeft(bottomLeftControls, 0);
double newTopForBottomLeftControls = SEImagesBitmap.Height;
if (putBottomControlsOnImage)
{
newTopForBottomLeftControls -= 40; // reasonably elegant appearance on bottom of image
if (newSize.Height < 1040)
{
// taskbar on a 1050 screen, take a bit more off
newTopForBottomLeftControls -= 24;
}
lastResizePutControlsOverImage = true;
TEXT_Mask.Foreground = Brushes.White;
rightSideControls.Height = newTopForBottomLeftControls + bottomLeftControls.Height; // shorten controls to be visible
}
else
{
rightSideControls.Height = SEImagesBitmap.Height + (initialRightSideControlsBounds.Height - initialBitmapSize.Height);
}
Canvas.SetTop(bottomLeftControls, newTopForBottomLeftControls);
Canvas.SetLeft(rightSideControls, SEImagesBitmap.Width + fireLegendGrid.Width);
// put bottomControls at bottom or for scales > 2 at right of bottomLeftControls
if (scale > 2)
{
// for some weird reason, alternatingVisibilityTools has an ActualWidth of zero
double widthTools = Math.Max(CommonToolsLayer.ActualWidth, DensityLayer.ActualWidth);
Canvas.SetLeft(bottomControls, bottomLeftControls.ActualWidth + widthTools);
Canvas.SetTop(bottomControls, newTopForBottomLeftControls);
}
else
{
Canvas.SetLeft(bottomControls, 0);
Canvas.SetTop(bottomControls, newTopForBottomLeftControls + bottomLeftControls.ActualHeight + 2);
}
NudgeWindowToFit();
}
}
base.OnRenderSizeChanged(sizeInfo);
}
/// <summary>
/// Abstracts the issue of determining size, which is complex now that controls may be moved by OnRenderSizeChanged().
/// </summary>
/// At scales of 3 or more, the controls are overlaid on the image.
private Size SizeAtScale(int tryScale)
{
double newWidth = (tryScale * initialBitmapSize.Width) + initialRightSideControlsBounds.Width + fireLegendGrid.Width +
initialWindowExtra.Width;
double newHeight = (tryScale * initialBitmapSize.Height) + initialWindowExtra.Height;
if (tryScale < 4)
{
// bottomLeftControls are under image
newHeight += bottomLeftControls.ActualHeight;
if (tryScale < 3)
{
// and other bottom controls stacked in two rows
newHeight += bottomControls.ActualHeight + 2;
}
}
return new Size(newWidth, newHeight);
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
... InitSizesOnceConstructed(); }
the entire XAML file:
<Window
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:dt="clr-namespace:DrawToolsLib;assembly=DrawToolsLib"
xmlns:ads="clr-namespace:ADS_Controls;assembly=ADS_UpDownControl"
xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
mc:Ignorable='d'
Height="367" Width="452" Top="2" Left="689"
Title='Spin-Echo Images' x:Class='Blah.SEImages'
Style="{StaticResource windowStyle}" >
<Window.Resources>
<Image x:Key="DrawRectangle" Width="16" Height="16" Source="img\DrawRectangle.bmp"/>
<Image x:Key="DrawRectangleDark" Width="16" Height="16" Source="img\DrawRectangleDark.bmp"/>
<Image x:Key="DrawOval" Width="16" Height="16" Source="img\DrawOval.bmp"/>
<Image x:Key="DrawOvalDark" Width="16" Height="16" Source="img\DrawOvalDark.bmp"/>
<Image x:Key="DrawFree" Width="16" Height="16" Source="img\DrawFree.bmp"/>
<Image x:Key="DrawFreeDark" Width="16" Height="16" Source="img\DrawFreeDark.bmp"/>
<Image x:Key="DrawSubQ" Width="16" Height="16" Source="img\DrawSubQ.bmp"/>
<Image x:Key="DrawSubQDark" Width="16" Height="16" Source="img\DrawSubQDark.bmp"/>
<Image x:Key="DrawCuts" Width="16" Height="16" Source="img\DrawCuts.bmp"/>
<Image x:Key="DrawCutsDark" Width="16" Height="16" Source="img\DrawCutsDark.bmp"/>
<Image x:Key="DrawEdge" Width="16" Height="16" Source="img\DrawEdge.bmp"/>
<Image x:Key="DrawEdgeDark" Width="16" Height="16" Source="img\DrawEdgeDark.bmp"/>
<Style TargetType="{x:Type ToggleButton}" x:Key="RectControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawRectangle}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawRectangleDark}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="OvalControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawOval}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawOvalDark}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="FreeControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawFree}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawFreeDark}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="ShowControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawSubQ}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawSubQDark}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="DarkControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawCuts}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawCutsDark}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type ToggleButton}" x:Key="Free2ControlStyle">
<Setter Property="Content" Value="{DynamicResource DrawEdge}" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="{DynamicResource DrawEdgeDark}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Canvas x:Name="outerCanvas" Margin="0,0,4,4" HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas x:Name="BitmapAndToolsOverlay" Width="305" Height="256" >
<Image x:Name="SEImagesBitmap" Margin="0" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<dt:DrawingCanvasMasking x:Name="drawingCanvas" Background="#00000000" VerticalAlignment="Top" Width="256" Height="256"/>
</Canvas>
<Grid x:Name="fireLegendGrid" Height="256" Width="40" Canvas.Left="305" Canvas.Top="0">
<Grid.RowDefinitions>
<RowDefinition Height="17"/>
<RowDefinition Height="*"/>
<RowDefinition Height="17"/>
<RowDefinition Height="*"/>
<RowDefinition Height="17"/>
<RowDefinition Height="*"/>
<RowDefinition Height="17"/>
</Grid.RowDefinitions>
<TextBlock x:Name='TEXT_WhiteMark' Grid.Column="0" Grid.Row="0" Text="3499"/>
<TextBlock x:Name='TEXT_YellowMark' Grid.Column="0" Grid.Row="2" Width='31' Text="2300" />
<TextBlock x:Name='TEXT_RedMark' Grid.Column="0" Grid.Row="4" Width='31' Text="1100"/>
<TextBlock x:Name='TEXT_BlackMark' Grid.Column="0" Grid.Row="6" Width='31' Text="0" />
</Grid>
<Grid x:Name="rightSideControls" Height="288" Canvas.Left="345" Canvas.Top="0" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="16"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Slider x:Name='CNTL_WhiteLevel' Grid.Column="0" Grid.Row="0" Maximum='255' Width="24" Minimum='0' Orientation='Vertical' Margin="0,4,0,4"/>
<Slider x:Name='CNTL_BlackLevel' Grid.Column="1" Grid.Row="0" Maximum='255' Width="24" Minimum='0' Orientation='Vertical' Margin="0,4,0,4"/>
<TextBlock x:Name='TEXT_WhiteSetting' Grid.Column="0" Grid.Row="1" Width='37' Height='16' HorizontalAlignment="Stretch" TextAlignment="Center" Text='1200'/>
<TextBlock x:Name='TEXT_BlackSetting' Grid.Column="1" Grid.Row="1" Width='37' Height='16' HorizontalAlignment="Stretch" TextAlignment="Center" Text='0'/>
<TextBlock x:Name='TEXT_Black' Grid.Column="0" Grid.Row="2" Height='16' HorizontalAlignment="Center" TextAlignment="Center"><Run Text="Black"/></TextBlock>
<TextBlock x:Name='TEXT_White' Grid.Column="1" Grid.Row="2" Height='16' TextAlignment="Center"><Run Text="White"/></TextBlock>
</Grid>
<StackPanel x:Name="bottomLeftControls" Height="27" Canvas.Left="0" Canvas.Top="256" Orientation="Horizontal" Margin="0,4">
<TextBlock x:Name='TEXT_Mask' Width='Auto' Height='Auto' Margin="4,0" VerticalAlignment="Center"><Run Text="Mask:"/></TextBlock>
<ComboBox x:Name='CNTL_ROIType' Width='88' Height="21" SelectedIndex="0" Margin="0,0,4,0">
<ComboBoxItem Content="Analysis"/>
<ComboBoxItem Content="Phantom"/>
<ComboBoxItem Content="Background"/>
<ComboBoxItem Content="Density"/>
</ComboBox>
<ComboBox x:Name='CNTL_Operation' Width='61' Height="21" SelectedIndex="0" Margin="4,0">
<ComboBoxItem Content="Add"/>
<ComboBoxItem Content="Cut"/>
</ComboBox>
<Button x:Name='CNTL_Clear' Width='23' Height='23' Margin="4,0,0,0">
<Image Width="16" Height="16" Source="img\ClearROI.bmp"/>
</Button>
<Canvas x:Name="alternatingVisibilityTools" Margin="0,3">
<StackPanel x:Name="CommonToolsLayer" Canvas.Top="-1" Canvas.Left="0" Orientation="Horizontal" Width="71" Height="23">
<ToggleButton x:Name='CNTL_CommonToolsLayer_Rectangle' Checked="CNTL_CommonToolsLayer_Rectangle_Click" Style="{StaticResource RectControlStyle}" />
<ToggleButton x:Name='CNTL_CommonToolsLayer_Oval' Checked="CNTL_CommonToolsLayer_Oval_Click" Style="{StaticResource OvalControlStyle}" />
<ToggleButton x:Name='CNTL_CommonToolsLayer_Free' Checked="CNTL_CommonToolsLayer_Free_Click" Style="{StaticResource FreeControlStyle}" />
</StackPanel>
<StackPanel x:Name="DensityLayer" Canvas.Top="-1" Canvas.Left="0" Orientation="Horizontal" Visibility="Hidden" Height="23">
<ToggleButton x:Name='CNTL_DensityLayer_Show' Checked="CNTL_DensityLayer_Show_Click" Style="{StaticResource ShowControlStyle}" />
<ToggleButton x:Name='CNTL_DensityLayer_Dark' Checked="CNTL_DensityLayer_Dark_Click" Style="{StaticResource DarkControlStyle}" />
<ToggleButton x:Name='CNTL_DensityLayer_Free' Checked="CNTL_DensityLayer_Free_Click" Style="{StaticResource Free2ControlStyle}" />
</StackPanel>
</Canvas>
</StackPanel>
<StackPanel x:Name='bottomControls' Orientation="Horizontal" Canvas.Left="0" Canvas.Top="291" Margin="4,0,0,0" Height="34" >
<Button x:Name='CNTL_Zoom' Width='27' Height="27" Click="CNTL_Zoom_Click">
<Image Width="21" Height="21" Source="img\DoZoom.bmp"/>
</Button>
<ads:ADS_UpDownControl x:Name="CNTL_ImageSwitch" Maximum='7' Minimum='0' Margin="4,0"/>
<TextBox x:Name='CNTL_ImageNames' MinWidth="180" Height="27" Text="TestCase3A.2_TE06.txt" Margin="4,0"/>
<Button x:Name='CNTL_ShowHeader' Height="24" Content="Show Header" Margin="4,0" Padding="4,0"/>
<Button x:Name='CNTL_SaveROI' Height="24" Content="Save ROI" Margin="4,0" Padding="4,0"/>
</StackPanel>
</Canvas>
</Window>
Place the Image control and the stackpanel in a grid and change the horizontal and vertical alignment of the stackpanel to Right and Bottom respectively. I think this should solve the problem.
精彩评论