通过媒体查询来实现|通过媒体查询来实现 WPF 响应式设计
【通过媒体查询来实现|通过媒体查询来实现 WPF 响应式设计】WPF 客户端经常需要运行在各种不同大小屏幕下,为了显示友好,所以开发的时候都需要考虑响应式设计。
布局往往通过指定比例,而不直接指定准确的大小来实现响应式布局(如 Width="3*" ),但是具体控件的大小(如 Thickness、CornerRadius)就没有开箱即用的响应式功能了,用 viewbox 来包装,比例就跟设计稿不一样了,看起来很怪。
嗐,所以又只能自己开发了!
实现目标
- 实现类似 css @media 媒体查询类似的功能。
- 设计稿都是 1920 × 1080 实现的,在 3840 × 2160 下,应该将所有控件的大小,边框放大两倍。
- 要考虑用户在系统下设定的缩放比例。
- 同事用起来要舒服,要支持热重载。
- 屏幕的 api 当然是白嫖别人写的库啦,我这里用的是 WpfScreenHelper。
public static class AppExtension
{
private static double? _factor;
///
/// 获取当前应用的缩放系数
/// 如果 4K 屏幕,需要放大 2 倍
///
///
///
public static double GetFactor(this Application app)
{
if (_factor is not null) return _factor.Value;
var screen = app.MainWindow?.GetScreen() ?? throw new ArgumentNullException(nameof(app.MainWindow));
_factor = screen.PixelBounds switch
{
{ Width: >= 3840, Height: >= 2160 } => screen.ScaleFactor / 2,
_ => screen.ScaleFactor
};
Debug.WriteLine($"屏幕大小: {screen.PixelBounds.Width} × {screen.PixelBounds.Height}");
Debug.WriteLine($"屏幕缩放: {screen.ScaleFactor * 100}%");
return _factor.Value;
}///
/// 根据屏幕大小和缩放系统,转换不同的数据类型
///
///
///
///
///
public static object ConvertForScreen(this Application app, object o) =>
o switch
{
double d => app.ConvertDoubleForScreen(d),
Thickness t => app.ConvertThicknessForScreen(t),
CornerRadius t => app.ConvertCornerRadiusForScreen(t),
_ => throw new NotSupportedException("不支持的转换类型")
};
public static double ConvertDoubleForScreen(this Application app, double value)
{
var factor = app.GetFactor();
return value / factor;
}public static Thickness ConvertThicknessForScreen(this Application app, Thickness value)
{
var factor = app.GetFactor();
return new Thickness(value.Left / factor, value.Top / factor, value.Right / factor, value.Bottom / factor);
}public static CornerRadius ConvertCornerRadiusForScreen(this Application app, CornerRadius value)
{
var factor = app.GetFactor();
return new CornerRadius(value.TopLeft / factor, value.TopRight / factor, value.BottomRight / factor,
value.BottomLeft / factor);
}///
/// 获取当前窗体所在的屏幕
///
/// 当前窗体
/// 窗体所在的屏幕
public static Screen GetScreen(this Window window)
{
var intPtr = new WindowInteropHelper(window).Handle;
//获取当前窗口的句柄
return Screen.FromHandle(intPtr);
//获取当前屏幕
}
}
自定义 xaml 标记
- 适配 Setter 上赋值和直接在控件上赋值的场景。
- 通过各种转换器来转换值。
public class ResponsiveSizeExtension : MarkupExtension
{
private static readonly Lazy _lazyDouble = new();
private static readonly Lazy _lazyThickness = new();
private static readonly Lazy _lazyCornerRadius = new();
private DoubleConverter _doubleConverter => _lazyDouble.Value;
private ThicknessConverter _thickConvert => _lazyThickness.Value;
private CornerRadiusConverter _cornerRadiusConvert => _lazyCornerRadius.Value;
[ConstructorArgument("value")]
public object Value { get;
set;
}public ResponsiveSizeExtension(object value)
{
if (value is string s && string.IsNullOrWhiteSpace(s)) value = "https://www.it610.com/article/0";
Value = https://www.it610.com/article/value;
}public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider;
var type = target switch
{
{ TargetObject: Setter setter } => setter.Property.PropertyType,
{ TargetProperty: DependencyProperty dp } => dp.PropertyType,
_ => throw new NotSupportedException($"不是 Setter 对象或者依赖属性")
};
TypeConverter converter = type switch
{
not null when type == typeof(double) => _doubleConverter,
not null when type == typeof(Thickness) => _thickConvert,
not null when type == typeof(CornerRadius) => _cornerRadiusConvert,
_ => throw new NotSupportedException($"{type} 类型不支持")
};
var originValue = https://www.it610.com/article/converter.ConvertFrom(Value) ?? throw new ArgumentException(nameof(Value));
var newValue = Application.Current.ConvertForScreen(originValue);
PrintLog(originValue, newValue);
return newValue;
}private void PrintLog(object originValue, object newValue) => Debug.WriteLine($"originValue: {originValue}, newValue: {newValue}, factor: {Application.Current.GetFactor()}");
}
使用
效果
屏幕大小: 1920 × 1080
屏幕缩放: 100%
originValue: 12,12,12,12, newValue: 12,12,12,12, factor: 1
originValue: 200, newValue: 200, factor: 1
originValue: 16, newValue: 16, factor: 1屏幕大小: 3840 × 2160
屏幕缩放: 100%
originValue: 12,12,12,12, newValue: 24,24,24,24, factor: 0.5
originValue: 200, newValue: 400, factor: 0.5
originValue: 16, newValue: 32, factor: 0.5屏幕大小: 3840 × 2160
屏幕缩放: 150%
originValue: 12,12,12,12, newValue: 16,16,16,16, factor: 0.75
originValue: 200, newValue: 266.6666666666667, factor: 0.75
originValue: 16, newValue: 21.333333333333332, factor: 0.75
最后
响应式设计在 WPF 应该还有很多实现方法,有方便的思路请留言教教我! 用模式匹配写逻辑是真舒服,建议大家都试试,代码简单又明了。 觉得对你有帮助点个推荐或者留言交流一下呗! 源码 https://github.com/yijidao/blog/tree/master/WPF/Responsive
推荐阅读
- 使用JPA+querydsl如何实现多条件动态查询
- python连接数据库后通过占位符添加数据
- Mybatis关联查询结果集对象嵌套的具体使用
- 提高跨库查询速度,你只需一个Smartbi
- elasticsearch|通过canal将MySQL数据同步到Elasticsearch
- MyCms|MyCms 自媒体 CMS 系统 v3.1.0,新增商城接口
- 二本计算机专业录取分数查询,二本录取查询
- 『现学现忘』Docker基础|『现学现忘』Docker基础 — 13、通过脚本安装Docker
- 『现学现忘』Docker基础|『现学现忘』Docker基础 — 12、通过RPM软件包方式安装Docker
- 通过|通过 hexo+serverless 快速搭建并部署一个自己的博客