我认为 C# win 窗体在某些情况下没有很好地重绘的问题在不同的地方都有涉及,但是,我没有设法通过使用我在网上找到的简单片段来解决我的问题。
我的问题:在一个表单上,我有一个 listView,我将其关联到一个自定义数据持有者(2 列、一个键和一个最后更新日期)。我需要从不同的地方调用 updateTime(key) 方法,然后在 GUI 中复制更改。模型已更改,但我的 listView 从未更改。
我有一个包含 ListView 的表单,如下所示:
partial class VolsPane : UserControl, IGUIPane
{
private ListView listView1;
private ListModel listModel1; //ListModel is 100% homemade
...
public VolsPane()
{
...
listModel1.setList(listView1);
}
}
为我的 listView 保存数据的类是这样的:
class ListModel
{
private Dictionary<string, DateTime> Underlying;
private ListView list;
...
public ListModel(string nexusKey)
{
...
}
...
public void setList(ListView list)
{
this.list = list;
}
public void updateTime(string ric)
{
Underlying[ric] = DateTime.UtcNow;
updateView();
}
public void updateView()
{
this.list.Clear();
this.list.Items.AddRange(this.underlyingToListItems());
}
...
public ListViewItem[] underlyingToListItems()
{
ListViewItem[] res = new ListViewItem[Underlying.Keys.Count];
int i = 0;
foreach (string ric in Underlying.Keys)
{
res[i] = new ListViewItem(new string[] { ric, Underlying[ric].ToString("MMM-dd hh:mm:ss") });
i++;
}
return res;
}
}
我确实意识到问题出在我的 updateView() 中。在调试中,代码肯定在那里。相信这个问题可以通过异步“调用”来解决,我引用了这篇似乎是引用的帖子:Stack overflow : Automating the invoke...
然后尝试这个:
private void updateView()
{
if (this.list.InvokeRequired)
{
this.list.Invoke(new MethodInvoker(() => { updateView(); }));
}
else
{
this.list.Items.Clear();
//this.list.Clear();
this.list.Items.AddRange(this.underlyingToListItems());
}
}
它构建但没有效果。在 Debug模式下,永远不会进入“if”分支,始终进入“else”分支。
然后这个:
private void updateView()
{
this.list.Invoke((MethodInvoker)delegate
{
this.list.Items.Clear();
//this.list.Clear();
this.list.Items.AddRange(this.underlyingToListItems());
});
}
我收到“InvalidOperationException:在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。”
我必须在这里遗漏什么明显的东西?或者我的问题实际上不是我想的那样?
谢谢大家!
最佳答案
你是对的,问题出在 updateView() 代码上。您确实需要调用 UI 线程,但问题是尚未为控件创建句柄。使用 WinForms 时的一个问题是,如果尚未创建句柄,则 InvokeRequired 实际上将返回 false。请参阅 MSDN documentation 中的解释:
如果控件的句柄尚不存在,InvokeRequired 会向上搜索控件的父链,直到找到确实具有窗口句柄的控件或窗体。如果找不到合适的句柄,InvokeRequired 方法将返回 false。
这就是为什么您对 InvokeRequired 的检查总是失败的原因。我已经看到这个问题以几种方式得到解决。一种解决方案是将回调附加到控件的句柄创建事件:
public class HandleHookedListView: ListView
{
private EventHandler _handleCreatedEvent;
public HandleHookedListView(): base()
{
_handleCreatedEvent = new EventHandler(HandleHookedControl_HandleCreated);
this.HandleCreated += _handleCreatedEvent;
}
private bool _handleIsCreated;
public bool HandleIsCreated
{
get { return _handleIsCreated; }
set { _handleIsCreated = value; }
}
void HandleHookedControl_HandleCreated(object sender, EventArgs e)
{
Debug.Print("Handle Created");
this.HandleIsCreated = true;
// Unhook the delegate
if (_handleCreatedEvent != null)
this.HandleCreated -= _handleCreatedEvent;
}
}
然后您将必须修改您的 updateView 以检查句柄是否已创建。在这种情况下,您的 ListView(列表)实例已替换为新的 HandleHookedListView
private void updateView()
{
var handleCreated = this.list.HandleIsCreated;
if (this.list.InvokeRequired && handleCreated)
{
// Handle is created and invoke is required.
this.list.Invoke(new MethodInvoker(() => { updateView(); }));
}
else if (handleCreated)
{
// In this case your control's handle has been created and invoke really
// isn't required go ahead and do the update
this.list.Items.Clear();
this.list.Items.AddRange(this.underlyingToListItems());
}
else
{
// You cannot update yet. The handle has not been created. Depending on if
// you need to "queue" these updates you can either collect them or just
// ignore them if a subsequent call to updateView() after the handle has been
// created will be sufficient
}
}
这里真正的关键是您正在尝试在控件完全初始化之前对其进行更新。
关于C# ListView 不更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8149787/