开发与分辨率无关的应用程序的技巧

发布于 2024-09-08 14:56:33 字数 689 浏览 6 评论 0原文

找到工作区测量值并在代码中设置一些属性以便将其绑定到 xaml 中 Control 的边距或高度/宽度属性是一个好习惯吗?

我这样做是为了让我的窗口根据可用的工作区域调整大小。

const int w = SystemParameters.WorkArea.Width;
const int h = SystemParameters.WorkArea.Height;

public Thickness OuterGridMargin { get; }

MainViewModel()
{
    OuterGridMargin = new Thickness(w/5,h/6,w/5,h/4);
}

xaml:

<Grid Margin="{Binding OuterGridMargin}" />

我对一些外部容器执行此操作,以便布局不会在较低分辨率下混乱。目前我在 20" 中以 1600x900 分辨率(96 dpi)工作。我的应用程序类似于小工具,没有常规窗口。

我想知道是否有一些替代方法。

搜索 [wpf] 分辨率]1 给出了很多解决类似问题的问题,但我仍然陷入困境,无法得出结论实现良好的与分辨率无关的布局。

Is it a good practice to find the workarea measurement and set some properties in code so that it could be bound to Control's margin or height/Width properties in xaml?

I do this so that my window would resize according to the available workarea.

const int w = SystemParameters.WorkArea.Width;
const int h = SystemParameters.WorkArea.Height;

public Thickness OuterGridMargin { get; }

MainViewModel()
{
    OuterGridMargin = new Thickness(w/5,h/6,w/5,h/4);
}

xaml:

<Grid Margin="{Binding OuterGridMargin}" />

I do this for some outer containers so that the layout would not be messed in lower resolutions. Currently I work at 1600x900 res(96 dpi) in a 20". My application is gadget like and does not have the regular window.

I want to know if there are some alternative approaches.

A search of [wpf] resolution]1 gives a lot of questions addressing similar problem but still I'm stuck and not able to come to a conclusion how to achieve a good resolution-independent layout.

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

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

发布评论

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

评论(4

べ映画 2024-09-15 14:56:34

WPF 中有两种处理分辨率的方法。

一种选择是设计为最小分辨率,并确保所有内容都适当停靠,以便元素随着窗口分辨率变大而变大。这就是很多人在 WinForms 中所做的事情,并且在 WPF 中仍然可以很好地工作。您可能已经对如何通过设置 Horizo​​ntalAlignment、VerticalAlignment 和边距来处理此问题有了一些概念。

在 WPF 中要做的更新、更时尚的事情在 WinForms 中几乎不可能做到,那就是让您的应用程序实际上只是放大,这样您的控件就会像窗口一样变大。为此,您将在窗口中的某些根元素上应用 ScaleTransform,并让 WPF 处理其余的事情。真的很酷。

为了展示它的样子,以下是启动应用程序时窗口的外观,将其变小,然后将其变大: https://i.sstatic.net/QeoVK.png

这是我制作的小型示例应用程序的隐藏代码:

public partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    #region ScaleValue Depdency Property
    public static readonly DependencyProperty ScaleValueProperty = DependencyProperty.Register("ScaleValue", typeof(double), typeof(MainWindow), new UIPropertyMetadata(1.0, new PropertyChangedCallback(OnScaleValueChanged), new CoerceValueCallback(OnCoerceScaleValue)));

    private static object OnCoerceScaleValue(DependencyObject o, object value)
    {
        MainWindow mainWindow = o as MainWindow;
        if (mainWindow != null)
            return mainWindow.OnCoerceScaleValue((double)value);
        else return value;
    }

    private static void OnScaleValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        MainWindow mainWindow = o as MainWindow;
        if (mainWindow != null)
            mainWindow.OnScaleValueChanged((double)e.OldValue, (double)e.NewValue);
    }

    protected virtual double OnCoerceScaleValue(double value)
    {
        if (double.IsNaN(value))
            return 1.0f;

        value = Math.Max(0.1, value);
        return value;
    }

    protected virtual void OnScaleValueChanged(double oldValue, double newValue) { }

    public double ScaleValue
    {            
        get => (double)GetValue(ScaleValueProperty);
        set => SetValue(ScaleValueProperty, value);
    }
    #endregion

    private void MainGrid_SizeChanged(object sender, EventArgs e) => CalculateScale();

    private void CalculateScale()
    {
        double yScale = ActualHeight / 250f;
        double xScale = ActualWidth / 200f;
        double value  = Math.Min(xScale, yScale);

        ScaleValue = (double)OnCoerceScaleValue(myMainWindow, value);
    }
}

以及 XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" 
    Name="myMainWindow"
    Width="200" Height="250">
<Grid Name="MainGrid" SizeChanged="MainGrid_SizeChanged">
    <Grid.LayoutTransform>
        <ScaleTransform x:Name="ApplicationScaleTransform"
                        CenterX="0"
                        CenterY="0"
                        ScaleX="{Binding ElementName=myMainWindow, Path=ScaleValue}"
                        ScaleY="{Binding ElementName=myMainWindow, Path=ScaleValue}" />
    </Grid.LayoutTransform>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center" Height="150">
        <TextBlock FontSize="20" Text="Hello World" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center"/>
        <Button Content="Button" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
    </Grid>
</Grid>

There are two ways to deal with resolution in WPF.

One option is to design to a minimum resolution and just make sure everything is docked appropriately so that the elements get larger as the Window resolution gets larger. This is how many people did things in WinForms and still works decently well for WPF. You probably already have some concept of how to deal with this by setting HorizontalAlignment, VerticalAlignment, and margins.

The newer, trendier thing to do in WPF that was nearly impossible to do in WinForms is have your application actually just zoom in so your controls get bigger as your Window does. To do this, you'll apply a ScaleTransform on some root element in your Window and let WPF take care of the rest. It's really cool.

To show what this is like, here's what a window would look like when you start the app, make it smaller, and make it bigger: https://i.sstatic.net/QeoVK.png

Here's the code-behind for the small sample app I made:

public partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    #region ScaleValue Depdency Property
    public static readonly DependencyProperty ScaleValueProperty = DependencyProperty.Register("ScaleValue", typeof(double), typeof(MainWindow), new UIPropertyMetadata(1.0, new PropertyChangedCallback(OnScaleValueChanged), new CoerceValueCallback(OnCoerceScaleValue)));

    private static object OnCoerceScaleValue(DependencyObject o, object value)
    {
        MainWindow mainWindow = o as MainWindow;
        if (mainWindow != null)
            return mainWindow.OnCoerceScaleValue((double)value);
        else return value;
    }

    private static void OnScaleValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        MainWindow mainWindow = o as MainWindow;
        if (mainWindow != null)
            mainWindow.OnScaleValueChanged((double)e.OldValue, (double)e.NewValue);
    }

    protected virtual double OnCoerceScaleValue(double value)
    {
        if (double.IsNaN(value))
            return 1.0f;

        value = Math.Max(0.1, value);
        return value;
    }

    protected virtual void OnScaleValueChanged(double oldValue, double newValue) { }

    public double ScaleValue
    {            
        get => (double)GetValue(ScaleValueProperty);
        set => SetValue(ScaleValueProperty, value);
    }
    #endregion

    private void MainGrid_SizeChanged(object sender, EventArgs e) => CalculateScale();

    private void CalculateScale()
    {
        double yScale = ActualHeight / 250f;
        double xScale = ActualWidth / 200f;
        double value  = Math.Min(xScale, yScale);

        ScaleValue = (double)OnCoerceScaleValue(myMainWindow, value);
    }
}

And the XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" 
    Name="myMainWindow"
    Width="200" Height="250">
<Grid Name="MainGrid" SizeChanged="MainGrid_SizeChanged">
    <Grid.LayoutTransform>
        <ScaleTransform x:Name="ApplicationScaleTransform"
                        CenterX="0"
                        CenterY="0"
                        ScaleX="{Binding ElementName=myMainWindow, Path=ScaleValue}"
                        ScaleY="{Binding ElementName=myMainWindow, Path=ScaleValue}" />
    </Grid.LayoutTransform>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center" Height="150">
        <TextBlock FontSize="20" Text="Hello World" Margin="5" VerticalAlignment="Top" HorizontalAlignment="Center"/>
        <Button Content="Button" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
    </Grid>
</Grid>
弱骨蛰伏 2024-09-15 14:56:34

JacobJ 的回答很好,我尝试了一下,效果非常好。

对于任何感兴趣的人,我做了一个附加行为,它做了同样的事情。我还添加了从 XAML 指定宽度/高度分母的选项。可以这样使用

<Grid Name="MainGrid"
      inf:ScaleToWindowSizeBehavior.Denominators="1000, 700"
      inf:ScaleToWindowSizeBehavior.ParentWindow="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
    <!--...-->
</Grid>

ScaleToWindowSizeBehavior

public static class ScaleToWindowSizeBehavior
{
    #region ParentWindow

    public static readonly DependencyProperty ParentWindowProperty =
        DependencyProperty.RegisterAttached("ParentWindow",
                                             typeof(Window),
                                             typeof(ScaleToWindowSizeBehavior),
                                             new FrameworkPropertyMetadata(null, OnParentWindowChanged));

    public static void SetParentWindow(FrameworkElement element, Window value)
    {
        element.SetValue(ParentWindowProperty, value);
    }

    public static Window GetParentWindow(FrameworkElement element)
    {
        return (Window)element.GetValue(ParentWindowProperty);
    }

    private static void OnParentWindowChanged(DependencyObject target,
                                              DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement mainElement = target as FrameworkElement;
        Window window = e.NewValue as Window;

        ScaleTransform scaleTransform = new ScaleTransform();
        scaleTransform.CenterX = 0;
        scaleTransform.CenterY= 0;
        Binding scaleValueBinding = new Binding
        {
            Source = window,
            Path = new PropertyPath(ScaleValueProperty)
        };
        BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleXProperty, scaleValueBinding);
        BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleYProperty, scaleValueBinding);
        mainElement.LayoutTransform = scaleTransform;
        mainElement.SizeChanged += mainElement_SizeChanged;
    }

    #endregion // ParentWindow

    #region ScaleValue

    public static readonly DependencyProperty ScaleValueProperty =
        DependencyProperty.RegisterAttached("ScaleValue",
                                            typeof(double),
                                            typeof(ScaleToWindowSizeBehavior),
                                            new UIPropertyMetadata(1.0, OnScaleValueChanged, OnCoerceScaleValue));

    public static double GetScaleValue(DependencyObject target)
    {
        return (double)target.GetValue(ScaleValueProperty);
    }
    public static void SetScaleValue(DependencyObject target, double value)
    {
        target.SetValue(ScaleValueProperty, value);
    }

    private static void OnScaleValueChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
    }

    private static object OnCoerceScaleValue(DependencyObject d, object baseValue)
    {
        if (baseValue is double)
        {
            double value = (double)baseValue;
            if (double.IsNaN(value))
            {
                return 1.0f;
            }
            value = Math.Max(0.1, value);
            return value;
        }
        return 1.0f;
    }

    private static void mainElement_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        FrameworkElement mainElement = sender as FrameworkElement;
        Window window = GetParentWindow(mainElement);
        CalculateScale(window);
    }

    private static void CalculateScale(Window window)
    {
        Size denominators = GetDenominators(window);
        double xScale = window.ActualWidth / denominators.Width;
        double yScale = window.ActualHeight / denominators.Height;
        double value = Math.Min(xScale, yScale);
        SetScaleValue(window, value);
    }

    #endregion // ScaleValue

    #region Denominators

    public static readonly DependencyProperty DenominatorsProperty =
        DependencyProperty.RegisterAttached("Denominators",
                                            typeof(Size),
                                            typeof(ScaleToWindowSizeBehavior),
                                            new UIPropertyMetadata(new Size(1000.0, 700.0)));

    public static Size GetDenominators(DependencyObject target)
    {
        return (Size)target.GetValue(DenominatorsProperty);
    }
    public static void SetDenominators(DependencyObject target, Size value)
    {
        target.SetValue(DenominatorsProperty, value);
    }

    #endregion // Denominators
}

Great answer by JacobJ, I tried it out and it worked perfectly.

For anyone who's interested I made an attached behavior which does the same thing. I also added the option to specify the width/height denominators from XAML. It can be used like this

<Grid Name="MainGrid"
      inf:ScaleToWindowSizeBehavior.Denominators="1000, 700"
      inf:ScaleToWindowSizeBehavior.ParentWindow="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
    <!--...-->
</Grid>

ScaleToWindowSizeBehavior

public static class ScaleToWindowSizeBehavior
{
    #region ParentWindow

    public static readonly DependencyProperty ParentWindowProperty =
        DependencyProperty.RegisterAttached("ParentWindow",
                                             typeof(Window),
                                             typeof(ScaleToWindowSizeBehavior),
                                             new FrameworkPropertyMetadata(null, OnParentWindowChanged));

    public static void SetParentWindow(FrameworkElement element, Window value)
    {
        element.SetValue(ParentWindowProperty, value);
    }

    public static Window GetParentWindow(FrameworkElement element)
    {
        return (Window)element.GetValue(ParentWindowProperty);
    }

    private static void OnParentWindowChanged(DependencyObject target,
                                              DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement mainElement = target as FrameworkElement;
        Window window = e.NewValue as Window;

        ScaleTransform scaleTransform = new ScaleTransform();
        scaleTransform.CenterX = 0;
        scaleTransform.CenterY= 0;
        Binding scaleValueBinding = new Binding
        {
            Source = window,
            Path = new PropertyPath(ScaleValueProperty)
        };
        BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleXProperty, scaleValueBinding);
        BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleYProperty, scaleValueBinding);
        mainElement.LayoutTransform = scaleTransform;
        mainElement.SizeChanged += mainElement_SizeChanged;
    }

    #endregion // ParentWindow

    #region ScaleValue

    public static readonly DependencyProperty ScaleValueProperty =
        DependencyProperty.RegisterAttached("ScaleValue",
                                            typeof(double),
                                            typeof(ScaleToWindowSizeBehavior),
                                            new UIPropertyMetadata(1.0, OnScaleValueChanged, OnCoerceScaleValue));

    public static double GetScaleValue(DependencyObject target)
    {
        return (double)target.GetValue(ScaleValueProperty);
    }
    public static void SetScaleValue(DependencyObject target, double value)
    {
        target.SetValue(ScaleValueProperty, value);
    }

    private static void OnScaleValueChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
    }

    private static object OnCoerceScaleValue(DependencyObject d, object baseValue)
    {
        if (baseValue is double)
        {
            double value = (double)baseValue;
            if (double.IsNaN(value))
            {
                return 1.0f;
            }
            value = Math.Max(0.1, value);
            return value;
        }
        return 1.0f;
    }

    private static void mainElement_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        FrameworkElement mainElement = sender as FrameworkElement;
        Window window = GetParentWindow(mainElement);
        CalculateScale(window);
    }

    private static void CalculateScale(Window window)
    {
        Size denominators = GetDenominators(window);
        double xScale = window.ActualWidth / denominators.Width;
        double yScale = window.ActualHeight / denominators.Height;
        double value = Math.Min(xScale, yScale);
        SetScaleValue(window, value);
    }

    #endregion // ScaleValue

    #region Denominators

    public static readonly DependencyProperty DenominatorsProperty =
        DependencyProperty.RegisterAttached("Denominators",
                                            typeof(Size),
                                            typeof(ScaleToWindowSizeBehavior),
                                            new UIPropertyMetadata(new Size(1000.0, 700.0)));

    public static Size GetDenominators(DependencyObject target)
    {
        return (Size)target.GetValue(DenominatorsProperty);
    }
    public static void SetDenominators(DependencyObject target, Size value)
    {
        target.SetValue(DenominatorsProperty, value);
    }

    #endregion // Denominators
}
┼── 2024-09-15 14:56:34

对 Fredrik Hedblad 的答案进行小修正:

因为您已在 Grid 元素中设置 DependencyProperty“分母”:

<Grid Name="MainGrid"
      inf:ScaleToWindowSizeBehavior.Denominators="1000, 700"
    <!--...-->
</Grid>

您必须使用网格调用 GetDominator 方法。
而不是:

private static void CalculateScale(Window window)
{
    var denominators = GetDenominators(window);
}

你必须使用这样的东西:

private static void mainElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var mainElement = sender as FrameworkElement;
    var window = GetParentWindow(mainElement);

    CalculateScale(window, mainElement);
}

private static void CalculateScale(Window window, FrameworkElement mainElement)
{
    var denominators = GetDenominators(mainElement);
}

Small correction to the answer of Fredrik Hedblad:

because you have set the DependencyProperty "Denominators" in the Grid element:

<Grid Name="MainGrid"
      inf:ScaleToWindowSizeBehavior.Denominators="1000, 700"
    <!--...-->
</Grid>

you must call the GetDominator method using the grid.
Instead of:

private static void CalculateScale(Window window)
{
    var denominators = GetDenominators(window);
}

you must use something like this:

private static void mainElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var mainElement = sender as FrameworkElement;
    var window = GetParentWindow(mainElement);

    CalculateScale(window, mainElement);
}

private static void CalculateScale(Window window, FrameworkElement mainElement)
{
    var denominators = GetDenominators(mainElement);
}
她如夕阳 2024-09-15 14:56:34

按照 JacobJ 先生的做法,我制作了自己的衍生作品。
我创建了一个接口,我们在此基础上进行了更改

public interface IResolutionDecorator
{
    double ActualWidth { get; }
    double ActualHeight { get; }
    double ResolutionHeight { get; set; }
    double ResolutionWidth { get; set; }
    object CurentContent { get; }
}

,对于这个接口,我

    #region IResolutionDecorator
    private static double OnCoerceScaleValue(double value)
    {
        return double.IsNaN(value) ? 1d : Math.Max(0.1, value);
    }

    public static void UpdateScale(this IResolutionDecorator source)
    {
        if (source.CurentContent is Visual visual)
        {
            double yScale = source.ActualHeight / source.ResolutionHeight;
            double xScale = source.ActualWidth / source.ResolutionWidth;
            double value = Math.Min(xScale, yScale);
            double ScaleValue = (double)OnCoerceScaleValue(value);
            visual.SetValue(Grid.LayoutTransformProperty, new ScaleTransform(ScaleValue, ScaleValue, 0, 0));
        }
    }
    #endregion

现在有了扩展,我们只需要添加主窗口中缺少的参数,并在主网格中的事件 sizeChanged 中设置 this.UpdateScale()

public partial class MainWindow : Window,  IResolutionDecorator
{
    public MainWindow()
    {
        InitializeComponent();
        this.Height = ResolutionHeight;
        this.Width = ResolutionWidth;
    }
    #region IResolutionDecorator
    public object CurentContent { get{ return this.Content; } }
    public double ResolutionHeight { get; set; } = 400d;
    public double ResolutionWidth { get; set; } = 800d;
    #endregion

    private void MainGrid_SizeChanged(object sender, EventArgs e)
    {
        this.UpdateScale();
    }
}

going with what mr JacobJ did I made my own spinoff.
I made a Interface on wich we base change

public interface IResolutionDecorator
{
    double ActualWidth { get; }
    double ActualHeight { get; }
    double ResolutionHeight { get; set; }
    double ResolutionWidth { get; set; }
    object CurentContent { get; }
}

and for this Interface i have extension

    #region IResolutionDecorator
    private static double OnCoerceScaleValue(double value)
    {
        return double.IsNaN(value) ? 1d : Math.Max(0.1, value);
    }

    public static void UpdateScale(this IResolutionDecorator source)
    {
        if (source.CurentContent is Visual visual)
        {
            double yScale = source.ActualHeight / source.ResolutionHeight;
            double xScale = source.ActualWidth / source.ResolutionWidth;
            double value = Math.Min(xScale, yScale);
            double ScaleValue = (double)OnCoerceScaleValue(value);
            visual.SetValue(Grid.LayoutTransformProperty, new ScaleTransform(ScaleValue, ScaleValue, 0, 0));
        }
    }
    #endregion

now we only need add Parameters that we lack in main window and set this.UpdateScale() in event sizeChanged in main grid

public partial class MainWindow : Window,  IResolutionDecorator
{
    public MainWindow()
    {
        InitializeComponent();
        this.Height = ResolutionHeight;
        this.Width = ResolutionWidth;
    }
    #region IResolutionDecorator
    public object CurentContent { get{ return this.Content; } }
    public double ResolutionHeight { get; set; } = 400d;
    public double ResolutionWidth { get; set; } = 800d;
    #endregion

    private void MainGrid_SizeChanged(object sender, EventArgs e)
    {
        this.UpdateScale();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文