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

C# 开发分辨率无关应用程序的技巧,c#,wpf,resolution-independence,C#,Wpf,Resolution Independence,在代码中查找工作区测量值并设置一些属性,以便可以将其绑定到xaml中控件的边距或高度/宽度属性,这是一种好的做法吗 我这样做是为了根据可用的工作区域调整窗口大小 const int w = SystemParameters.WorkArea.Width; const int h = SystemParameters.WorkArea.Height; public Thickness OuterGridMargin { get; } MainViewModel() { OuterGri

在代码中查找工作区测量值并设置一些属性,以便可以将其绑定到xaml中控件的边距或高度/宽度属性,这是一种好的做法吗

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

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:


我对一些外部容器这样做是为了在较低的分辨率下布局不会混乱。目前我在一个20英寸的屏幕上以1600x900分辨率(96 dpi)工作,我的应用程序类似于小工具,没有常规窗口

我想知道是否有其他方法


搜索[wpf]resolution]会提出许多问题来解决类似的问题,但我仍然被卡住了,无法得出如何实现良好的分辨率独立布局的结论。

在wpf中有两种处理分辨率的方法

一种选择是设计到最低分辨率,只需确保所有内容都正确对接,以便元素随着窗口分辨率的增大而变大。这就是有多少人在WinForms中做了一些事情,但仍然可以很好地用于WPF。您可能已经有了一些如何通过设置HorizontalAli来处理这一问题的概念垂直对齐、垂直对齐和边缘

在WPF中要做的更新、更时尚的事情在WinForms中几乎是不可能做到的,就是让你的应用程序实际上只是放大,这样你的控件就会随着你的窗口变大。要做到这一点,你需要在你的窗口中的某个根元素上应用ScaleTransform,让WPF来处理其余的事情。这真的很酷

为了说明这是什么样子,下面是启动应用程序时窗口的外观,使其变小,变大:

以下是我制作的小样本应用程序的代码:

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>

雅各布的回答很好,我试过了,效果很好

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

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

Fredrik Hedblad回答的小更正:

因为您已经在网格元素中设置了DependencyProperty“Denominators”:

<Grid Name="MainGrid"
      inf:ScaleToWindowSizeBehavior.Denominators="1000, 700"
    <!--...-->
</Grid>
您必须使用以下内容:

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);
}

按照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中设置此.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();
    }
}

+1为简单、清晰和完整起见,这似乎不公平。您也可以将所有内容都放在Viewbox中以实现相同的效果。不,Viewbox没有相同的效果,并且有其他限制。即,如何指定“本机”选项“解决方案是应用程序的起点。视图框只是缩放以填充边界。此机制还允许您根据需要设置最小和最大缩放距离。也许你只想放大,或者你只想缩小;在我看来这是不对的。OnCerceScaleValue将第一个参数强制转换到主窗口,因此传入网格将简单地返回该值,而不会强制执行该值。我认为您需要调用oncercascalevalue(主窗口,value);相反。请注意,布局转换不适用于分离的树,如工具提示和上下文菜单。您的答案看起来很有趣,但我在XAML和C#方面仍然太差,无法让它工作。我缺少了“inf:”在它的XAML部分的用法…@AndreaAntonangeli“inf:”是在使用AttachedProperty之前声明的名称空间,即
xmlns:inf=“clr namespace:My.App.attachedrops”
。这实际上非常有效,而且可能会将其限制在视图的某些部分!干得好+1还不够。请在发布时删除额外的选项卡,这不需要很多努力
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
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();
    }
}