通过媒体查询来实现 WPF 响应式设计
WPF 客户端经常需要运行在各种不同大小屏幕下,为了显示友好,所以开发的时候都需要考虑响应式设计。
布局往往通过指定比例,而不直接指定准确的大小来实现响应式布局(如 Width="3*" ),但是具体控件的大小(如 Thickness、CornerRadius)就没有开箱即用的响应式功能了,用 viewbox 来包装,比例就跟设计稿不一样了,看起来很怪。
嗐,所以又只能自己开发了!
实现目标
- 实现类似 css @media 媒体查询类似的功能。
- 设计稿都是 1920 × 1080 实现的,在 3840 × 2160 下,应该将所有控件的大小,边框放大两倍。
- 要考虑用户在系统下设定的缩放比例。
- 同事用起来要舒服,要支持热重载。
实现逻辑
根据屏幕大小和屏幕缩放比例来计算缩放系数。
- 屏幕的 api 当然是白嫖别人写的库啦,我这里用的是 WpfScreenHelper。
public static class AppExtension
{
private static double? _factor;
/// <summary>
/// 获取当前应用的缩放系数
/// 如果 4K 屏幕,需要放大 2 倍
/// </summary>
/// <param name="app"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 根据屏幕大小和缩放系统,转换不同的数据类型
/// </summary>
/// <param name="app"></param>
/// <param name="o"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
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);
}
/// <summary>
/// 获取当前窗体所在的屏幕
/// </summary>
/// <param name="window">当前窗体</param>
/// <returns>窗体所在的屏幕</returns>
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<DoubleConverter> _lazyDouble = new();
private static readonly Lazy<ThicknessConverter> _lazyThickness = new();
private static readonly Lazy<CornerRadiusConverter> _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 = "0";
Value = 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 = 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()}");
}
使用
<Window
...
xmlns:local="clr-namespace:Responsive" >
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="{local:ResponsiveSize 19}" />
</Style>
</Window.Resources>
<Border
Margin="50"
BorderBrush="LightSeaGreen"
BorderThickness="{local:ResponsiveSize 12}">
<Grid Width="{local:ResponsiveSize 200}" Background="LightBlue">
<TextBlock Text="Test" />
</Grid>
</Border>
</Window>
效果
屏幕大小: 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
通过媒体查询来实现 WPF 响应式设计的更多相关文章
- 怎样使用CSS3媒体查询(Media Queries)制作响应式网站
自本周开始博主将开始同大家一起研究响应式web设计,CSS3 Media Queries是入门,本周更新,博主将给大家分享media queries的一些常用的用法及注意事项. Media Queri ...
- 关于媒体查询 @Media Screen 与响应式
其实CSS2中已经有了媒体查询的概念,但是CSS3中媒体查询功能更加的强大! 首先,我们来看一个小例子 设置媒体查询的 Max Width ,改变窗口大小到600px的时候就会触发一下代码: @med ...
- 响应式设计的思考:媒体查询(media query)
Jason Grigsby发表了篇文章,<CSS Media Query for Mobile is Fool’s Gold>对媒体查询(media query)吐槽,大意是在移动设备上使 ...
- 【media-queries】媒体查询,为了响应式设计而生
目录 简介 语法 常用尺寸 一 简介 针对现在纷杂的设备,css3中加入,可以查询你的浏览类型(screen彩色屏幕, print, all)和css属性判断. 最常用的就是查询屏幕大小,给予适合的展 ...
- Bootstrap 响应式设计
本教程讲解如何在网页布局中应用响应式设计.在课程中,您将学到响应式 Web 设计.随着移动设备的普及,如何让用户通过移动设备浏览您的网站获得良好的视觉效果,已经是一个不可避免的问题了.响应式 Web ...
- HTML5、CSS3响应式设计——笔记
1.1.响应式网页设计 响应式网页设计(RWD,Responsive Web Design)这个术语,由伊桑·马科特(EthanMarcotte)提出.他在A List Apart 发表了一篇开创性的 ...
- web设计经验<一> 提升移动设备响应式设计的8个建议
今天看到一些关于web设计的一些建议和设计经验,拿出来分享分享. 第一篇: 提升移动设备响应式设计的8个建议 一.直观性和易用性 在使用移动设备时,对于杂乱.复杂或者不直观的设计造成的混乱不佳的用户体 ...
- 前端响应式设计中@media等的相关运用
现在做前端响应式网站特别,响应式成为现在前端设计一个热点,它成为热点的最主要的原因就是,移动端设备屏幕的种类多样,那么如何设置响应式屏幕. /*打印样式*/ @mediaprint{color:red ...
- CSS 响应式设计
响应式设计是指在不同分辨率的设备中,网页布局可以自适应的调整.这种弹性化的布局使网站在不同设备中的布局都比较合理,可以为不同终端的用户提供更加舒适的界面和更好的用户体验,其根本理念是使原本 PC 上的 ...
随机推荐
- Java 变量的声明及初始化
格式: 数据类型 变量名字1 , 变量名字2 ,--变量名字n ; 案例: int i 声明了一个整形的变量. double d 声明了一个double数据类型的变量 float f 声明了一个fl ...
- 学习jsp篇:jsp简单实例之二登录
编程环境:IDEA,Tomcat,JavaEE 一.实例二登录 1.在自己建的工程下的web目录下建一个文件夹为login,在login中编写登录代码(其实就是和实例一同一个项目) 2.先建登录页面j ...
- 还在做廉价的劳动力?部署PXE实现Kickstart无人值守安装
搭建PXE实现Kickstart无人值守安装 1.搭建PXE远程安装服务器 2.实现kicstart无人值守安装 1.PXE介绍及搭载: PXE是有Intel公司开发的网络引导技术,工作在Client ...
- DNS域名解析之正向解析
DNS域名解析之正向解析 1.DNS介绍 2.DNS正向解析实验 1.DNS定义:DNS是"域名系统"的英文缩写.它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地 ...
- JS快速入门(一)
目录 Javascript快速入门(一) 变量的命名规则 变量与常量 变量 常量 数据类型 数值型:Number 字符串:String 常用方法: 布尔类型:Boolean 未定义类型 :Undefi ...
- 如何在 pyqt 中实现全局事件总线
前言 在 Qt 中可以使用信号和槽机制很方便地实现部件之间的通信,考虑下面这样的场景: 我想要点击任意一个专辑卡并通知主界面跳转到专辑界面,那么一种实现方式如上图所示:点击任意一个蓝色方框所示的专辑卡 ...
- pm2 部署 vue
链接:我整理了一个网站, 用来介绍一些有意思和实用工具的网站, 我没有打包, 直接甩上去了 因为vue是用 npm run dev 来运行的, 你用pm2 npm run dev 是错误的 需要加上- ...
- 基于Oracle数据库登陆界面及功能实现 Java版
首先要在Oracle数据库创建表文件,包括建立表头以及关键字(唯一标识符),此次程序所用的表名称为SW_USER_INFO,表头有UNAME.UKEY.USEX等,关键字为UCC,然后添加一条记录,用 ...
- Python语法进阶(1)- 进程与线程编程
1.进程与多进程 1.1.什么是进程 进程就是程序执行的载体 什么叫多任务? 多任务就是操作系统可以同时运行多个任务.比如你一边在用浏览器学习,还一边在听音乐,,这就是多任务,至少同时有3个任务正在运 ...
- 【高频Java面试题】简单说说JVM堆的内存结构和GC回收流程
目录 前言 JVM堆内存结构简述 JVM堆内存结构图 堆初体验 结构详情 新生代 老年代 永久代/元空间 GC回收流程 GC回收流程图 GC回收详细流程 查看JDK自带可视化堆空间图 总结 前言 我们 ...