【问题标题】:Auto Position Window - WPF自动定位窗口 - WPF
【发布时间】:2017-09-18 09:23:40
【问题描述】:

我创建了一些窗口,禁用了边框并设置了 SizeToContent="Height"。

xaml:

<Window Title="info" Width="350" ResizeMode="NoResize" WindowStyle="None" Opacity="1" Background="{x:Null}" AllowsTransparency="True" SizeToContent="Height">
   <Grid>
     <StackPanel Name="maingrid" Background="AliceBlue">
     </StackPanel>
   </Grid>
</Window>

当我打开这个窗口时,我把它放在屏幕右下角:

private void setposition()
{
   var primaryMonitorArea = SystemParameters.WorkArea;
   this.Left = primaryMonitorArea.Right - this.Width;
   this.Top = primaryMonitorArea.Bottom - this.Height;
}

有时我尝试将子级添加到 ma​​ingrid,然后重新定位它,因为高度已更改:

Border brd = new Border();
DockPanel.SetDock(brd, Dock.Top);
brd.Margin =new System.Windows.Thickness(0,5,0,0);
DockPanel dpanel = new DockPanel();
System.Windows.Controls.Label header = new System.Windows.Controls.Label();
header.Content = "test";
DockPanel.SetDock(header, Dock.Top);
dpanel.Children.Add(header);
brd.Child = dpanel;
maingrid.Children.Add(brd);
setposition();
System.Windows.Forms.Timer tmer = new System.Windows.Forms.Timer();
tmer.Tick += (sender, e) => timertick(brd, tmer);
tmer.Interval = 5 * 1000;
tmer.Enabled = true;

之后,窗口位置在垂直平面中随机定位。 5 秒后,我破坏了它的边框并尝试重新定位窗口

private void timertick(Border brd, System.Windows.Forms.Timer timer)
{
  maingrid.Children.Remove(brd);
  timer.Enabled = false;
  setposition();
  timer.Dispose();         
}

垂直位置又有一些奇怪的值。

屏幕位置:

【问题讨论】:

  • 在 WPF 中,您必须等待渲染或强制布局(通过自己调用 measure/arrange),然后才能使用新尺寸。顺便说一句,使用DispatcherTimer
  • 窗口渲染后如何等待?
  • 我不确定到底是什么问题(到底出了什么问题?),这是我的疯狂评论。但如果这是正确的猜测,那么只需 Dispatcher.Invoke(() =&gt; methodToRunAfterRender, DispatcherPriority.Render) 就可以了。

标签: c# wpf


【解决方案1】:

您能否更准确地说明您的期望并尝试用语言表达您的问题?

如果您尝试定位窗口,为什么不使用 WindowStartupLocation

将 WindowStartupLocation 设置为 Manual 会使窗口根据其 Left 和 Top 属性值定位。如果未指定 Left 或 Top 属性,则它们的值由 Windows 确定。 设置 CenterScreen 会导致一个窗口位于包含鼠标光标的屏幕中心。

将 WindowStartupLocation 设置为 CenterOwner 会导致窗口 位于其所有者窗口的中心(请参阅所有者),如果 指定的。所有者窗口可以是另一个 WPF 窗口或 非 WPF 窗口。

MSDN source

【讨论】:

【解决方案2】:

在您调用setposition(); 的地方,窗口大小尚未实现,获取实际大小的最简单方法是通过将操作放入调度程序队列来等待渲染。

考虑这个例子:

public MainWindow()
{
    InitializeComponent();
    Loaded += (s, e) => Title = $"{ActualHeight}"; // 39
    var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1), IsEnabled = true };
    timer.Tick += (s, e) =>
    {
        stackPanel.Children.Add(new TextBlock { Text = "123", Width = 200, Height = 20 });
        Title = $"{ActualHeight}"; // 39, 59, 79 ... wrong
        // below is correct way, displays 59, 79 ...
        //Dispatcher.Invoke(() => Title = $"{ActualHeight}", DispatcherPriority.Render);
    };
}

这里我有一个窗口,它的大小可以容纳并有stackPanel

<Window SizeToContent="Height" Width="200" ...>
    <StackPanel x:Name="panel" />
</Window>

计时器每秒都会向其添加新的TextBlock

现在,如果我尝试像这样更新Title,那么ActualHeight 仍然是旧的,第一次更新后第一个值是39,这是错误的(应该是20 + 39 = 59)。发生这种情况是因为添加子项不会立即重新计算所有内容并进行渲染。不会。相反,所有此类操作(测量、排列和渲染)都通过调度程序排队并在一段时间后发生。这就是 WPF 的工作原理。

要解决这个问题,我可以简单地告诉“请在渲染后进行”

Dispatcher.Invoke(() => ..., DispatcherPriority.Render);

【讨论】:

    【解决方案3】:

    我发现,maingrid.ActualHeight 在渲染窗口之前有实际高度,然后这样做:

    this.Top = primaryMonitorArea.Bottom - maingrid.ActualHeight;
    

    它工作正常。

    【讨论】:

      猜你喜欢
      • 2011-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-25
      • 1970-01-01
      • 2012-02-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多