XAML:将控件放置在图像上方

3
我需要在Grid单元格中显示一个Image。在图像上方,必须添加一个控件的StackPanel(例如缩放、亮度等),位于图像的右下角。如何做到这一点?我正在尝试以下方法,但不确定如何将控件的StackPanel定位在图像的右下角。即使用户调整浏览器窗口大小,位置也需要保持不变。
<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>

不,StackPanel 的位置取决于 GridWidthHeight。即使使用 Auto 也不起作用。 - Tsu
为什么你要在边框、画布和图像之间添加这么多元素,看起来过于复杂。请更详细地描述你的预期布局。如果你有缩放控件,当图像超出包含网格的大小时,你希望发生什么?在缩放后,将控件保留在图像的右下角是否合理? - AnthonyWJones
3个回答

3

由于UI意图有些不清晰,所以我现在会先保留不给出更完整的答案。但是,如果您只是有一张图片,并且想要在该图片的右下角叠加控件,则Grid是解决方案:

<Grid>
    <Image x:Name="img" Stretch="None" />
    <StackPanel x:Name="control" VerticalAlignment="Bottom" HorizontalAlignment="Right">
        <!-- controls here -->
    </StackPanel>
</Grid>

这个网格将根据图像的大小调整大小(除非它比控制面板小),控制面板将漂浮在图像的右下角。这是一种简单即是美的情况,让组件发挥作用。

由于您的控件之一是“缩放”,我怀疑您将需要解决其他问题,这可能最终使这个问题无关紧要,但以上是您现在需要的要点。


0

我曾经在WPF中通过重写protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)解决了类似的问题。

当用户放大屏幕时,我需要重新排列一些东西,以便可以保持纵横比调整图像大小,并为3倍和4倍缩放腾出空间,必须移动控件并将其覆盖在图像上。

我通过使用最外层的Canvas元素,在其中嵌套了几个Grid来处理大部分布局和需要移动或叠加在StackPanels中的控件集。

请注意,我尝试通过从初始布局驱动逻辑来避免过多的硬编码 - 如果您在XAML中稍微重新排列一下,它仍应该能够根据与几个关键对象相关的大小调整物品。

    /**
     * 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(); }

整个XAML文件:

<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>

0
将Image控件和StackPanel放入网格中,并将StackPanel的水平和垂直对齐方式分别更改为Right和Bottom。我认为这应该可以解决问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接