tips on developing resolution independent application

前端 未结 4 2119
时光说笑
时光说笑 2020-12-04 12:59

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?

相关标签:
4条回答
  • 2020-12-04 13:09

    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);
    }
    
    0 讨论(0)
  • 2020-12-04 13:18

    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
    }
    
    0 讨论(0)
  • 2020-12-04 13:23

    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: http://i.stack.imgur.com/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>
    
    0 讨论(0)
  • 2020-12-04 13:31

    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();
        }
    }
    
    0 讨论(0)
提交回复
热议问题