鳕鱼天空

This is Mr Wang's Tech Blog.

c# datagridview加双缓中解决闪烁问题

摘要: 本文介绍了在C#Winform中,当DataGridView和ListView加载大量数据时遇到卡顿和闪烁问题的解决方案。方法一是重写CreateParams属性启用双缓冲,方法二是通过反射设置控件的DoubleBuffered属性。两种方法都能提高控件的绘制性能并减少闪烁。

先说原因,经过测试发现,当datagridview中加载大量数据时,拖拽进行放大缩小时,会有明显卡顿的感觉

解决办法1:

      /// <summary>
      /// 重写CreateParams属性的get访问器,目的是为了设置控件的窗口样式,以启用双缓冲功能,解决闪烁问题
      /// </summary>
      protected override CreateParams CreateParams
      {
          get
          {
              //首先获取基类的CreateParams属性,保存到变量cp中。这些参数包含了控件的基本创建信息
              CreateParams cp = base.CreateParams;
     
              //对cp的扩展样式进行位或操作(|=),将0x02000000这个标志添加到现有样式中。
              //这个特定的十六进制值对应于WS_EX_COMPOSITED窗口样式,
              //它可以强制控件及其所有子控件进行全部重新绘制,实现双缓冲效果,进而提升控件的绘制性能,减少闪烁。
              cp.ExStyle |= 0x02000000;
              return cp;
          }
     
      }

解决办法2:

创建一个类,在方法内部设置控件的双缓冲属性

    public static class DoubleBufferedDataGridView
     {
         /// <summary>
         /// 通过双缓冲技术,解决DataGridView或ListView的闪烁问题
         /// </summary>
         public static void DoubleBufferedDataGirdView(this DataGridView dgv, bool flag)
         {
             //获取传入的DataGridView对象的类型。
             Type dgvType = dgv.GetType();
     
             //使用反射技术查找DataGridView类型中的一个非公共实例属性
             PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)!;
             
             //调用PropertyInfo对象的SetValue方法来设置DataGridView控件的DoubleBuffered属性值
             pi.SetValue(dgv, flag, null);
         }
     }

然后在构造方法中调用该方法即可

    public Form1()
    {
        InitializeComponent();
      
        //设置双缓冲,解决闪烁
        dataGridView1.DoubleBufferedDataGirdView(true);
    }

参考链接:两种方法使用双缓冲解决C# 中DataGridView和ListView 实时刷新数据时出现闪烁的问题

使用双缓冲技术解决winform窗体控件卡顿(dataGridView加载数据缓慢)

原文链接:https://blog.csdn.net/weixin_49297068/article/details/137453565

 

.NET8 Blazor的Auto渲染模式的初体验

合集 - .NET8(6)

1..NET8依赖注入新特性Keyed services2.C#12中的Primary Constructors(主构造函数)

2.C#12中的Primary Constructors(主构造函数)

3..NET8 Blazor的Auto渲染模式的初体验

4..NET8 Blazor新特性 流式渲染5.C#12中的Collection expressions(集合表达式语法糖)6.将 .NET Aspire 部署到 Kubernetes 集群

5.C#12中的Collection expressions(集合表达式语法糖)6.将 .NET Aspire 部署到 Kubernetes 集群

6.将 .NET Aspire 部署到 Kubernetes 集群

 

.NET8发布后,Blazor支持四种渲染方式

  1. 静态渲染,这种页面只可显示,不提供交互,可用于网页内容展示
  2. 使用Blazor Server托管的通过Server交互方式
  3. 使用WebAssembly托管的在浏览器端交互方式
  4. 使用Auto自动交互方式,最初使用 Blazor Server,并在随后访问时使用 WebAssembly 自动进行交互式客户端呈现。 自动呈现通常会提供最快的应用启动体验。

体验

通过VS创建Blazor应用时,选择Blazor Web App这个新模板。过程中可以看到有四种模板可供选择。我们可以选择Auto来体验。  

新建Auto后,可以看到项目模板为我们创建了两个项目:BlazorApp与BlazorApp.Client

其中BlazorApp为启动项目,BlazorApp.Client为一个组件库

接着可以看看启动项目中的Program,一个明显的变化是,.NET8中的blazor通过添加插件方式开启了Blazor Server与WebAssembly两种交互方式。不加的话其实就是静态模式。

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

...
    
app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);

交互模式的设置可以在Blazor.App.Client项目中的Counter中看到,使用了一个指令来设置渲染方式@rendermode InteractiveAuto

运行

我们可以尝试将项目运行起来,切换到counter路由并查看他如何自动切换交互方式。

首先,请将devtools中Application Tab页中的Cache Storage清空,防止已缓存的wasm文件影响测试效果。

然后,可以通过devtools中的request blocking功能先将wasm全部block。

 

我们可以发现虽然wasm都请求失败,但是Auto模式采用Blazor Server的方式通信,在点击按钮后,交互仍然生效

然后我们可以去掉对wasm的block,重新刷新页面,并点击Counter按钮后,wasm交互也生效

结论

因此验证Auto模式下,在wasm下载未完成或失败的情况下,使用Blazor Server方式交互。在wasm下载完成后使用WebAssembly方式在浏览器端交互,提高用户的体验。

另外,如果想体验静态交互,可以将Counter组件中的渲染方式@rendermode InteractiveAuto去掉,即可体验静态交互的方式,静态交互的方式中点击Counter按钮,将不再有响应事件发生。

 

iphone的safari浏览器中实现全屏浏览的方法

正常情况下,当你用手机浏览器打开网页时,导航就停留在上面,这样实际展示的屏幕就变小了。
那能不能加载后,屏幕就自动全屏呢?这就是本文要讨论的。

Add to Home Screen

说到全屏不得不谈iPhone下的safari有一个特别且重要的功能就是“Add to Home Screen”。(就在Safari浏览器最下方,最中间的那个位置,点击选择即可)
这个功能类似于把网页地址作为一个超链接的方式放到手机桌面,并且可以直接访问。不过要注意的是每个链接都需要js进行一次特殊处理,那就是监听页面点击事件,如果是链接,则使用window.location = this.href;,这样页面就不会从当前的本地窗口跳到浏览器了。
那我们看看具体代码是怎么处理的。
其实只需要在HEAD代码里增加一些必要数据:

正常情况下,当你用手机浏览器打开网页时,导航就停留在上面,这样实际展示的屏幕就变小了。
那能不能加载后,屏幕就自动全屏呢?这就是本文要讨论的。
 
Add to Home Screen
 
说到全屏不得不谈iPhone下的safari有一个特别且重要的功能就是“Add to Home Screen”。(就在Safari浏览器最下方,最中间的那个位置,点击选择即可)
这个功能类似于把网页地址作为一个超链接的方式放到手机桌面,并且可以直接访问。不过要注意的是每个链接都需要js进行一次特殊处理,那就是监听页面点击事件,如果是链接,则使用window.location = this.href;,这样页面就不会从当前的本地窗口跳到浏览器了。
那我们看看具体代码是怎么处理的。
其实只需要在HEAD代码里增加一些必要数据:
 
复制代码代码如下:
<meta name="apple-mobile-web-app-capable" content="yes" /><!-- home screen app 全屏 -->
<meta name="apple-mobile-web-app-status-bar-style" content="black" /><!-- 状态栏 -->
<!-- 还需要额外设置不同尺寸的启动图,默认不设置的话会自动去寻找根目录下的apple-touch-icon-precomposed.png -->
<!-- home screen app iPhone icon -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="startup/apple-touch-icon-57x57-precomposed.png" />
<!-- home screen app iPad icon -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="startup/apple-touch-icon-72x72-precomposed.png" />
<!-- home screen app iPhone Retinas icon -->
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="startup/apple-touch-icon-114x114-precomposed.png" />
<!-- home screen app iPad Retinas icon -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="startup/apple-touch-icon-144x144-precomposed.png" />
<!-- iPhone5启动图 -->
<link rel="apple-touch-startup-image" href="startup/startup5.png" media="(device-height:568px)">
<!-- iPhone4启动图 -->
<link rel="apple-touch-startup-image" size="640x920" href="startup/startup.png" media="(device-height:480px)">
 
还想了解具体的设置可以参考苹果的官网说明:Configuring Web Applications
当然,对启动图,我推荐的做法是只使用一张114*114的图片即可。即:
复制代码代码如下:
<link rel="apple-touch-icon-precomposed" href="startup/apple-touch-icon-114x114-precomposed.png" />
全屏js代码
 
复制代码代码如下:
window.addEventListener('DOMContentLoaded', function() {
    var page = document.getElementById('page'),
        nav = window.navigator,
        ua = nav.userAgent,
        isFullScreen = nav.standalone;
    if (ua.indexOf('Android') !== -1) {
        // 56对应的是Android Browser导航栏的高度
        page.style.height = window.innerHeight + 56 + 'px';
    } else if (/iPhone|iPod|iPad/.test(ua)) {
        // 60对应的是Safari导航栏的高度
        page.style.height = window.innerHeight + (isFullScreen ? 0 : 60) + 'px'
    }
    setTimeout(scrollTo, 0, 0, 1);
}, false);
 
这段代码本质上就是当前窗口的高度 + 导航栏的高度 获取到真实的屏幕高度。最后再调用scrollTo方法。

还想了解具体的设置可以参考苹果的官网说明:Configuring Web Applications
当然,对启动图,我推荐的做法是只使用一张114*114的图片即可。即:

复制代码代码如下:

<link rel="apple-touch-icon-precomposed" href="startup/apple-touch-icon-114x114-precomposed.png" />

全屏js代码

复制代码代码如下:

window.addEventListener('DOMContentLoaded', function() {
    var page = document.getElementById('page'),
        nav = window.navigator,
        ua = nav.userAgent,
        isFullScreen = nav.standalone;

    if (ua.indexOf('Android') !== -1) {
        // 56对应的是Android Browser导航栏的高度
        page.style.height = window.innerHeight + 56 + 'px';
    } else if (/iPhone|iPod|iPad/.test(ua)) {
        // 60对应的是Safari导航栏的高度
        page.style.height = window.innerHeight + (isFullScreen ? 0 : 60) + 'px'
    }
    setTimeout(scrollTo, 0, 0, 1);
}, false);

这段代码本质上就是当前窗口的高度 + 导航栏的高度 获取到真实的屏幕高度。最后再调用scrollTo方法。

不可不知的C#基础 1. -- Extension 扩展方法

背景

前几天有同事问到我一个简单的功能, 就是当你使用枚举时如何给每个一元素增加描述字符串并且可以很容易的读取出来.

 

比如有一个枚举类型是列出对一个问题给出的选项(例如: 同意?不同意?中立?):

public enum AssessmentAnswer
 {
     Strongly_Disagree = 1,
     Disagree = 2,
     Neutral = 3,
     Agree = 4,
     Strongly_Agree = 5
 }


当选择不同的答案时, 希望得到一些描述性的语句比方说:

1. "强烈的反对"

2. "反对"

3. "持中立观点"

4. "同意"

5. "完全的同意"

 

当然你可以建立一个数据库表格,存放这些信息, 但是我想起几年前,我在网上下载一个 EnumDescription 源码(忘记了是在哪里下载的), 就可以实现这一功能.

让我们一起来看看是怎么样用 Extension 做到的:

实现

首先定义一个EnumDescription 类:

public class EnumDescription : Attribute
{
     public string Text
     {
         get { return _text; }
     } private string _text;

     public EnumDescription(string text)
     {
         _text = text;
     }
}

注意它的父类是Attribute, 因为我们希望将描述的语句作为每一个对应元素的特性. 然后新建一个文件,命名EnumExtensions.cs

public static class EnumExtensions
{
    public static string ToDescription(this Enum enumeration)
    {
        Type type = enumeration.GetType();
        MemberInfo[] memInfo = type.GetMember(enumeration.ToString());

        if (null != memInfo && memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(EnumDescription), false);
            if (null != attrs && attrs.Length > 0)
                return ((EnumDescription)attrs[0]).Text;
        }

        return enumeration.ToString();
    }
}

在这里我们定义了一个扩展函数ToDescription, 就像所有的扩展函数一样,它的参数是类似(this …),

这个函数首先用GetType 得到了当前枚举的类型, 然后借助 GetMember按照元素的名字(值), 得到这个特定的元素, 最后用GetCustomAttributes 得到描述的内容.

 

这个扩展函数实现后,我们可以修改枚举的定义,加入描述:

public enum AssessmentAnswer
{
[EnumDescription("强烈的反对")]
Strongly_Disagree = 1,

    [EnumDescription("反对")]
    Disagree = 2,
    Neutral = 3,
    Agree = 4,
    [EnumDescription("完全的同意")]
    Strongly_Agree = 5
}

当要获取描述语句时, 你可以轻松的调用ToDescription :

//返回 "强烈的反对"

AssessmentAnswer.Strongly_Disagree.ToDescription()

注意, 如果没有给元素加入特性, 你仍旧可以使用ToDescription, 可以参考上面的代码看看是为什么.

//返回 “Disagree”

AssessmentAnswer.Disagree.ToDescription()

总结

扩展方法作为特殊的静态方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。

对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。

 

给初学者的话: 一旦当你实现扩展方法后, 你只需要复制dll 添加文件命名空间; 或者复制cs文件到你的项目中, 就可以使用这些方法,  但是建议除非你有充分的理由才实现扩展方法。

很多我们要用到的扩展方法都可以在网上得到, 所以一般在写自己的扩展前请先搜索一下.

 

 

 

 

微信小程序 页面监听全局变量的变化

前言

在前段时间的开发过程中,遇到了一个需要监听是否有推送的需求,需要在不同的页面监听全局变量从而进行条件渲染,因此总结了一下便有了下篇文章.

介绍

当我们开发一个大型的微信小程序时,通常会涉及到多个页面或组件之间的数据传递和共享,而全局变量可以方便地实现这一需求。但是当全局变量的值发生变化时,我们需要及时地更新页面或组件的数据,以保证用户界面的实时性。这时候,我们就需要用到监听器的机制。

监听器是一种设计模式,用于在对象状态发生改变时,自动调用特定的函数。在微信小程序中,我们可以通过定义一个全局变量来存储所有的监听器函数,并在需要监听的页面或组件中注册监听器函数,以实现对全局变量的监听。当全局变量的值发生改变时,我们就可以通过遍历所有的监听器函数,并依次调用这些函数,及时更新页面或组件的数据。

示例

下面是一个在微信小程序中实现监听器机制的示例代码:

在 App.js 中定义全局变量和注册监听器方法

App({
  globalData: {
    currentUser: null, // 全局变量 currentUser
    listeners: [], // 存储监听器函数的数组
  },
  // 注册监听器函数的方法
  registerListener: function (listener) {
    this.globalData.listeners.push(listener);
  },
  // 触发监听器函数的方法
  triggerListeners: function () {
    var listeners = this.globalData.listeners;
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]();
    }
  }
})

在需要监听的页面或组件中注册监听器函数

Page({
  onLoad: function () {
    // 注册监听器函数
    getApp().registerListener(this.onCurrentUserChange.bind(this));
  },
  onCurrentUserChange: function () {
    // 全局变量 currentUser 的值发生改变时,调用该函数
    this.setData({
      currentUser: getApp().globalData.currentUser
    })
  }
})

在全局变量发生改变时触发监听器函数

getApp().globalData.currentUser = {
  name: '小明',
  age: 20
};
getApp().triggerListeners();