c# - 无法在 CSharp 中使用 Win32 消息填充 TreeView 控件
问题描述
我正在与TreeView Control
inWindows Forms
合作C#
。
由于实际人口部分TreeView Control
需要大量时间,因此会冻结 UI。因此,我尝试使用PostMessage Win32 API
后台线程进行填充,但我发现Treeview
没有插入 Items。
所以我将代码从后台线程移动到主线程。但是插入项目代码也不起作用。我得到了类似的代码TreeView
,并尝试使用互操作例程C++
来做同样的事情。C#
我不会采用通常的C#
方式,treeView1.Nodes.Add("...")
因为即使我遵循从另一个线程填充 UI 控件的Delegate
方法和方法,它也会冻结 UI。BackgroundWorker
我在下面给出我使用的代码。有人可以帮助找到代码的问题。
另请注意,对于TreeView
控件,我使用的是从类派生的自己的简单类TreeView
,在该类中我重写了WndProc
验证 Windows 消息流的方法,并且我可以看到消息(TVM_INSERTITEM
)实际上正在通过,但该项目仍然没有得到人口稠密
此外,我从用于控制的后台线程中获得了类似的互操作代码,ListView
但我的尝试TreeView
到目前为止还没有成功。
表单类代码
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public uint lParam;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVINSERTSTRUCT
{
public IntPtr hParent;
public IntPtr hInsertAfter;
public TVITEM item;
}
public enum TreeViewInsert
{
TVI_ROOT = -0x10000,
}
[Flags]
public enum TreeViewItemMask
{
TVIF_TEXT = 0x0001,
}
public partial class Form1 : Form
{
const int TV_FIRST = 0x1100;
IntPtr tvInsItemPtr;
TVINSERTSTRUCT tvins;
IntPtr handleTreeView;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
public enum TreeViewMessage
{
TVM_INSERTITEM = TV_FIRST + 50,
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
//treeView1.Nodes.Add("hello");
PopulateTree(handleTreeView);
}
public void PopulateTree(IntPtr handle)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product";
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(treeView1.Handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
//SendMessage(treeView1.Handle, (int)TreeViewMessage.TVM_INSERTITEM, 0, tvInsItemPtr);
}
}
}
MyTreeView 类代码
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace UpdateTreeViewFromAnotherThread
{
class MyTreeView:TreeView
{
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x1132)
{
TVINSERTSTRUCT anotherTVInsertStruct;
anotherTVInsertStruct = (TVINSERTSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(TVINSERTSTRUCT));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVInsertStruct.item.pszText);
}
if(m.Msg == 0x113F)
{
TVITEM anotherTVItem;
anotherTVItem = (TVITEM)Marshal.PtrToStructure(m.LParam, typeof(TVITEM));
string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVItem.pszText);
}
base.WndProc(ref m);
//Trace.WriteLine(m.Msg.ToString() + ", " + m.ToString());
}
}
}
更新_1
使用以下代码阻止了 Treeview 的 NM_CUSTOMDRAW。感谢链接处的代码。
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_REFLECT + WM_NOTIFY:
{
NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
switch ((int)nmhdr.code)
{
case NM_CUSTOMDRAW:
NMTVCUSTOMDRAW nmTvDraw = (NMTVCUSTOMDRAW)m.GetLParam(typeof(NMTVCUSTOMDRAW));
switch (nmTvDraw.nmcd.dwDrawStage)
{
case CDDS_ITEMPREPAINT:
m.Result = (IntPtr)CDRF_DODEFAULT;
break;
}
Marshal.StructureToPtr(nmTvDraw, m.LParam, false);
return;
}
break;
}
}
base.WndProc(ref m);
}
所以现在如果我更改我之前的 PopulateTree 函数(注意 Thread.Sleep())并将其调用到后台线程,如下所示,它不会在填充过程中冻结 UI
private void button1_Click(object sender, EventArgs e)
{
handleTreeView = treeView1.Handle;
Thread backgroundThread = new Thread(() => PopulateTree(handleTreeView));
backgroundThread.Start();
}
public void PopulateTree(IntPtr handle)
{
for(int i =0; i< 1000; i++)
{
tvins = new TVINSERTSTRUCT();
tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;
// Set the text of the item.
string productName = "Product_" + i.ToString();
string value = productName;
byte[] buffer = new byte[100];
buffer = Encoding.Unicode.GetBytes(value + "\0");
tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);
tvins.hParent = IntPtr.Zero;
tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
PostMessage(handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
Thread.Sleep(1000);
}
}
解决方案
感谢 Jimi 和 MikiD,我能够使用 BeginUpdate 和 BeginInvoke 方法产生相同的非冻结 UI 行为。我改变了我的代码如下
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => PopulateTree());
}
private async void PopulateTree()
{
for(int i = 0;i< 1000;i++)
{
treeView1.BeginInvoke( (MethodInvoker)delegate ()
{
treeView1.BeginUpdate();
treeView1.Nodes.Add("Product_" + i.ToString());
treeView1.EndUpdate();
}
);
System.Threading.Thread.Sleep(1000);
}
}
推荐阅读
- django - 即使 required=False 也需要 Django REST Framework 序列化程序字段
- amazon-web-services - 确保 ECS 仅在新任务准备就绪时终止旧任务
- python - 根据分隔符垂直拆分 DataFrame
- amazon-web-services - 设置 AWS_PROFILE 不适用于 powershell
- powershell - Azure CLI 异常处理
- mysql - mySQL - 带有连接语句和格式表的双重求和
- html - 使用 XSLT 将 XML 转换为 HTML 表格
- html - 根据全名和出生日期建议用户名
- css - 将 ReactJS 组件的高度设置为 div
- c++ - SDL_BlitSurface 抛出分段错误错误