如何在 WPF 窗口中只允许统一调整大小?

发布于 2024-07-10 12:48:05 字数 75 浏览 5 评论 0原文

我不希望我的窗口“仅水平”或“仅垂直”调整大小。 我可以在窗口上设置一个属性来强制执行此操作,或者是否可以使用一个漂亮的代码隐藏技巧?

I don't want my window to be resized either "only horizontally" or "only vertically." Is there a property I can set on my window that can enforce this, or is there a nifty code-behind trick I can use?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

蓝眼泪 2024-07-17 12:48:05

您可以使用 WPF 的 ViewBox 保留内容的宽高比,并使用内部固定宽度和高度的控件。

让我们尝试一下。 您可以更改ViewBox的“Stretch”属性来体验不同的结果。

这是我的屏幕截图: 在此处输入图像描述

<Window x:Class="TestWPF.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <Viewbox Stretch="Uniform">
        <StackPanel Background="Azure" Height="400" Width="300" Name="stackPanel1" VerticalAlignment="Top">
            <Button Name="testBtn" Width="200" Height="50">
                <TextBlock>Test</TextBlock>
            </Button>
        </StackPanel>
    </Viewbox>

</Window>

You can reserve aspect ratio of contents using WPF's ViewBox with control with fixed width and height inside.

Let's give this a try. You can change "Stretch" attribute of ViewBox to experience different results.

Here is my screeen shot: enter image description here

<Window x:Class="TestWPF.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <Viewbox Stretch="Uniform">
        <StackPanel Background="Azure" Height="400" Width="300" Name="stackPanel1" VerticalAlignment="Top">
            <Button Name="testBtn" Width="200" Height="50">
                <TextBlock>Test</TextBlock>
            </Button>
        </StackPanel>
    </Viewbox>

</Window>
喜你已久 2024-07-17 12:48:05

您始终可以处理 WM_WINDOWPOSCHANGING 消息,这使您可以在调整大小过程中控制窗口大小和位置(而不是在用户完成调整大小后修复问题)。

以下是在 WPF 中执行此操作的方法,我从多个来源合并了此代码,因此其中可能存在一些语法错误。

internal enum WM
{
   WINDOWPOSCHANGING = 0x0046,
}

[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
   public IntPtr hwnd;
   public IntPtr hwndInsertAfter;
   public int x;
   public int y;
   public int cx;
   public int cy;
   public int flags;
}

private void Window_SourceInitialized(object sender, EventArgs ea)
{
   HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
   hwndSource.AddHook(DragHook);
}

private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
   switch ((WM)msg)
   {
      case WM.WINDOWPOSCHANGING:
      {
          WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
          if ((pos.flags & (int)SWP.NOMOVE) != 0)
          {
              return IntPtr.Zero;
          }

          Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
          if (wnd == null)
          {
             return IntPtr.Zero;
          }

          bool changedPos = false;

          // ***********************
          // Here you check the values inside the pos structure
          // if you want to override tehm just change the pos
          // structure and set changedPos to true
          // ***********************

          if (!changedPos)
          {
             return IntPtr.Zero;
          }

          Marshal.StructureToPtr(pos, lParam, true);
          handeled = true;
       }
       break;
   }

   return IntPtr.Zero;
}

You can always handle the WM_WINDOWPOSCHANGING message, this let's you control the window size and position during the resizing process (as opposed to fixing things after the user finished resizing).

Here is how you do it in WPF, I combined this code from several sources, so there could be some syntax errors in it.

internal enum WM
{
   WINDOWPOSCHANGING = 0x0046,
}

[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
   public IntPtr hwnd;
   public IntPtr hwndInsertAfter;
   public int x;
   public int y;
   public int cx;
   public int cy;
   public int flags;
}

private void Window_SourceInitialized(object sender, EventArgs ea)
{
   HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
   hwndSource.AddHook(DragHook);
}

private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
   switch ((WM)msg)
   {
      case WM.WINDOWPOSCHANGING:
      {
          WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
          if ((pos.flags & (int)SWP.NOMOVE) != 0)
          {
              return IntPtr.Zero;
          }

          Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
          if (wnd == null)
          {
             return IntPtr.Zero;
          }

          bool changedPos = false;

          // ***********************
          // Here you check the values inside the pos structure
          // if you want to override tehm just change the pos
          // structure and set changedPos to true
          // ***********************

          if (!changedPos)
          {
             return IntPtr.Zero;
          }

          Marshal.StructureToPtr(pos, lParam, true);
          handeled = true;
       }
       break;
   }

   return IntPtr.Zero;
}
谈情不如逗狗 2024-07-17 12:48:05

这就是我的解决方案。

您需要将其添加到您的控件/窗口标记中:

Loaded="Window_Loaded"

并且您需要将其放在后面的代码中:

private double aspectRatio = 0.0;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    aspectRatio = this.ActualWidth / this.ActualHeight;
}

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    if (sizeInfo.WidthChanged)
    {
        this.Width = sizeInfo.NewSize.Height * aspectRatio;
    }
    else
    {
        this.Height = sizeInfo.NewSize.Width * aspectRatio;
    }
}

我尝试了 Viewbox 技巧,但我不喜欢它。 我想将窗口边框锁定为特定大小。 这是在窗口控件上进行测试的,但我认为它也适用于边框。

This is what my solution was.

You will need to add this to your control/window tag:

Loaded="Window_Loaded"

And you will need to place this in your code behind:

private double aspectRatio = 0.0;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    aspectRatio = this.ActualWidth / this.ActualHeight;
}

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    if (sizeInfo.WidthChanged)
    {
        this.Width = sizeInfo.NewSize.Height * aspectRatio;
    }
    else
    {
        this.Height = sizeInfo.NewSize.Width * aspectRatio;
    }
}

I tried the Viewbox trick and I did not like it. I wanted to lock the window border to a specific size. This was tested on a window control but I assume it would work on a border as well.

生来就爱笑 2024-07-17 12:48:05

您可以尝试复制我经常在 Flash 视频网站上看到的效果。 它们允许您以任何您喜欢的方式扩展浏览器窗口,但仅拉伸演示区域,使其适合高度或宽度中的最小值。

例如,如果您垂直拉伸窗口,您的应用程序将不会调整大小。 它可以简单地在显示区域的顶部和底部添加黑条并保持垂直居中。

这对于 WPF 来说可能可行,也可能不可能; 我不知道。

You could try replicating an effect that I often see on Flash Video websites. They allow you to expand the browser window any way you like, but only stretch the presentation area so that it fits the smallest of the height or width.

For example, if you stretch the window vertically, your application would not resize. It would simple add black bars to the top and bottom of the display area and remain vertically centered.

This may or may not be possible with WPF; I don't know.

吲‖鸣 2024-07-17 12:48:05

这可能有点晚了,但你可以简单地将它放在你的代码后面......

Private Sub UserControl1_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
    If e.HeightChanged Then
        Me.Width = Me.Height
    Else
        Me.Height = Me.Width
    End If
End Sub

This may be bit late but you can simply put it in your code behind....

Private Sub UserControl1_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
    If e.HeightChanged Then
        Me.Width = Me.Height
    Else
        Me.Height = Me.Width
    End If
End Sub
忘你却要生生世世 2024-07-17 12:48:05

我原以为您可以使用值转换器将宽度与高度双向绑定以保持纵横比。 将纵横比作为转换器参数传递将使其更通用。

所以,我尝试了这个 - 首先没有转换器的绑定:

<Window 
    ...
    Title="Window1" Name="Win" Height="500" 
    Width="{Binding RelativeSource={RelativeSource self}, 
                    Path=Height, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <StackPanel>
        <TextBlock>Width:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Width}" />
        <TextBlock>Height:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Height}" />
    </StackPanel>    
</Window>

奇怪的是,绑定的行为就好像它是单向的,并且报告的窗口宽度(如 TextBlock 中所示)与屏幕上的大小不一致!

这个想法可能值得追求,但需要首先解决这种奇怪的行为。

希望有帮助!

I had expected that you could two-way bind the width to the height using a value converter to maintain aspect ratio. Passing the aspect ratio as the converter parameter would make it more general purpose.

So, I tried this - the binding with no converter first:

<Window 
    ...
    Title="Window1" Name="Win" Height="500" 
    Width="{Binding RelativeSource={RelativeSource self}, 
                    Path=Height, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <StackPanel>
        <TextBlock>Width:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Width}" />
        <TextBlock>Height:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Height}" />
    </StackPanel>    
</Window>

Strangely, the binding is behaving as if it is one-way and the reported width of the window (as shown in the TextBlock) is not consistent with it's size on screen!

The idea might be worth pursuing, but this strange behavior would need to be sorted out first.

Hope that helps!

怀念你的温柔 2024-07-17 12:48:05

在代码示例中:

if (sizeInfo.WidthChanged)     
{         
    this.Width = sizeInfo.NewSize.Height * aspectRatio;    
}     
else     
{         
    this.Height = sizeInfo.NewSize.Width * aspectRatio; 
} 

我相信第二个计算应该是:

this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);  

我在“SizeChanged”事件处理程序中对这项工作进行了变体。 由于我希望宽度成为控制尺寸,因此我只是通过计算形式强制高度与其匹配:

if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.  
this.Height = this.ActualWidth * (1 / aspectRatio);

您可能会注意到对aspectRatio >的检查。 0,...我这样做是因为我发现它倾向于调用我的处理程序,在“Load”方法甚至分配aspectRatio之前执行调整大小。

In the code sample:

if (sizeInfo.WidthChanged)     
{         
    this.Width = sizeInfo.NewSize.Height * aspectRatio;    
}     
else     
{         
    this.Height = sizeInfo.NewSize.Width * aspectRatio; 
} 

I believe the second computation should be:

this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);  

I made a variation of this work in a "SizeChanged" event handler. Since I wanted the width to be the controlling dimension, I simply forced the height to match to it with a computation of the form:

if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.  
this.Height = this.ActualWidth * (1 / aspectRatio);

You may note the check for an aspectRatio > 0, ... I did this because I found that it was tending to call my handlers that did the resizing before the "Load" method had even assigned the aspectRatio.

灯角 2024-07-17 12:48:05

也许为时已晚,但我从 Mike O'Brien 博客中找到了一个解决方案,而且效果非常好。
http://www.mikeobrien.net/blog/maintaining-aspect-调整大小时的比例/
以下是他博客中的代码:

<Window ... SourceInitialized="Window_SourceInitialized" ... >
    ...
Window>

public partial class Main : Window
{
    private void Window_SourceInitialized(object sender, EventArgs ea)
    {
        WindowAspectRatio.Register((Window)sender);
    }
    ...
}


internal class WindowAspectRatio
{
    private double _ratio;

    private WindowAspectRatio(Window window)
    {
        _ratio = window.Width / window.Height;
        ((HwndSource)HwndSource.FromVisual(window)).AddHook(DragHook);
    }

    public static void Register(Window window)
    {
        new WindowAspectRatio(window);
    }

    internal enum WM
    {
        WINDOWPOSCHANGING = 0x0046,
    }

    [Flags()]
    public enum SWP
    {
        NoMove = 0x2,
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    }

    private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
    {
        if ((WM)msg == WM.WINDOWPOSCHANGING)
        {
            WINDOWPOS position = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));

            if ((position.flags & (int)SWP.NoMove) != 0 || 
                HwndSource.FromHwnd(hwnd).RootVisual == null) return IntPtr.Zero;

            position.cx = (int)(position.cy * _ratio);

            Marshal.StructureToPtr(position, lParam, true);
            handeled = true;
        }

        return IntPtr.Zero;
    }
}

Maybe too late, but i found a solution from Mike O'Brien blog, and it work really good.
http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/
Below is code from his blog:

<Window ... SourceInitialized="Window_SourceInitialized" ... >
    ...
Window>

public partial class Main : Window
{
    private void Window_SourceInitialized(object sender, EventArgs ea)
    {
        WindowAspectRatio.Register((Window)sender);
    }
    ...
}


internal class WindowAspectRatio
{
    private double _ratio;

    private WindowAspectRatio(Window window)
    {
        _ratio = window.Width / window.Height;
        ((HwndSource)HwndSource.FromVisual(window)).AddHook(DragHook);
    }

    public static void Register(Window window)
    {
        new WindowAspectRatio(window);
    }

    internal enum WM
    {
        WINDOWPOSCHANGING = 0x0046,
    }

    [Flags()]
    public enum SWP
    {
        NoMove = 0x2,
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    }

    private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
    {
        if ((WM)msg == WM.WINDOWPOSCHANGING)
        {
            WINDOWPOS position = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));

            if ((position.flags & (int)SWP.NoMove) != 0 || 
                HwndSource.FromHwnd(hwnd).RootVisual == null) return IntPtr.Zero;

            position.cx = (int)(position.cy * _ratio);

            Marshal.StructureToPtr(position, lParam, true);
            handeled = true;
        }

        return IntPtr.Zero;
    }
}
清君侧 2024-07-17 12:48:05

我找到了一种不依赖于 Windows 平台特定 API 的方法,并且具有可接受的用户体验(拖动窗口时不会摇晃)。 它使用计时器在 0.1 秒后调整窗口大小,这样用户就不会看到它晃动。

public partial class MainWindow : Window
{
    private DispatcherTimer resizeTimer;
    private double _aspectRatio;
    private SizeChangedInfo? _sizeInfo;

    public MainWindow()
    {
        InitializeComponent();
        _aspectRatio = Width / Height;
        resizeTimer = new DispatcherTimer();
        resizeTimer.Interval = new TimeSpan(100*10000); // 0.1 seconds
        resizeTimer.Tick += ResizeTimer_Tick;
    }

    private void ResizeTimer_Tick(object? sender, EventArgs e)
    {
        resizeTimer.Stop();
        if (_sizeInfo == null) return;
        var percentWidthChange = Math.Abs(_sizeInfo.NewSize.Width - _sizeInfo.PreviousSize.Width) / _sizeInfo.PreviousSize.Width;
        var percentHeightChange = Math.Abs(_sizeInfo.NewSize.Height - _sizeInfo.PreviousSize.Height) / _sizeInfo.PreviousSize.Height;

        if (percentWidthChange > percentHeightChange)
            this.Height = _sizeInfo.NewSize.Width / _aspectRatio;
        else
            this.Width = _sizeInfo.NewSize.Height * _aspectRatio;
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        _sizeInfo = sizeInfo;
        resizeTimer.Stop();
        resizeTimer.Start();
        base.OnRenderSizeChanged(sizeInfo);
    }
}

I got a way that doesn't depend on Windows platform-specific API also with acceptable user experience (not shake while dragging the window). It uses a timer to adjust the window size after 0.1 seconds so user won't see it shakes.

public partial class MainWindow : Window
{
    private DispatcherTimer resizeTimer;
    private double _aspectRatio;
    private SizeChangedInfo? _sizeInfo;

    public MainWindow()
    {
        InitializeComponent();
        _aspectRatio = Width / Height;
        resizeTimer = new DispatcherTimer();
        resizeTimer.Interval = new TimeSpan(100*10000); // 0.1 seconds
        resizeTimer.Tick += ResizeTimer_Tick;
    }

    private void ResizeTimer_Tick(object? sender, EventArgs e)
    {
        resizeTimer.Stop();
        if (_sizeInfo == null) return;
        var percentWidthChange = Math.Abs(_sizeInfo.NewSize.Width - _sizeInfo.PreviousSize.Width) / _sizeInfo.PreviousSize.Width;
        var percentHeightChange = Math.Abs(_sizeInfo.NewSize.Height - _sizeInfo.PreviousSize.Height) / _sizeInfo.PreviousSize.Height;

        if (percentWidthChange > percentHeightChange)
            this.Height = _sizeInfo.NewSize.Width / _aspectRatio;
        else
            this.Width = _sizeInfo.NewSize.Height * _aspectRatio;
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        _sizeInfo = sizeInfo;
        resizeTimer.Stop();
        resizeTimer.Start();
        base.OnRenderSizeChanged(sizeInfo);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文