将MessageBox.Show(...)
置于BeginInvoke
内导致应用程序冻结。
DoDragDrop
似乎是问题的一部分。如果单击选项卡而不拖动,则不会冻结。
所有内容都在UI线程上执行,因此我认为代码是按原样编写的。
代码中是否有问题,或者这是.NET中的错误?
我已经在.NET 3.5 4.0和4.5上测试过,每个都有相同的结果。
这是一个展示行为的最小剥离。
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace DynamicTests {
public class Form2 : Form {
public Form2() {
TabControl2 tc = new TabControl2 { Dock = DockStyle.Fill };
for (int i = 1; i <= 5; i++) {
TabPage tp = new TabPage("Tab Page " + i);
tc.TabPages.Add(tp);
}
tc.SelectedIndexChanged += tc_SelectedIndexChanged;
this.Controls.Add(tc);
this.StartPosition = FormStartPosition.CenterScreen;
this.Size = new Size(600, 600);
}
void tc_SelectedIndexChanged(object sender, EventArgs e) {
// click and drag a tab + BeginInvoke results in the message
// freezing the application
this.BeginInvoke((Action) delegate {
MessageBox.Show(this, "Congrats, the program is locked.");
// one possible solution is to show the message on a thread
// but the form cannot be used as the parent window
//Thread t = new Thread(() => {
// MessageBox.Show("Congrats, the program is locked.");
//});
//t.Start();
});
}
}
public class TabControl2 : TabControl {
private TabPage dragTab2 = null; // tab currently being dragged
public TabControl2() : base() {
this.AllowDrop = true; // required for dragging tabs
}
private int HotTabIndex { get; set; }
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
dragTab2 = null;
if (HotTabIndex >= 0) {
dragTab2 = this.TabPages[HotTabIndex];
}
}
bool cancelSelecting = false;
// For tabs with a [x] close button, if the user clicks the close button, it doesn't make sense to first show the tab and then close it.
// Also, some tabs are lazy loaded. The Control the TabPage is hosting is not initialized until the tab is selected. So it's a short
// but noticeable time that the control is created (and it may query a database taking more time)... only to be closed.
//
// The solution is to intercept the left mouse button and set cancelSelecting to true. Then in the OnSelecting(...) method, what would
// normally happen is the tab appears right away. However, because the user might have clicked the close button, the selection of the
// tab was moved to the OnMouseUp(...) method.
//
// This behavior is similar to windows. If you click the [X] on a window, and hold the mouse, the window does not close.
// Thus, you can move the mouse off the button if you change your mind.
protected override void DefWndProc(ref Message m) {
const int WM_LBUTTONDOWN = 0x201;
if (m.Msg == (int) WM_LBUTTONDOWN) {
cancelSelecting = true;
}
base.DefWndProc(ref m);
}
protected override void OnSelecting(TabControlCancelEventArgs e) {
base.OnSelecting(e);
e.Cancel = cancelSelecting;
}
protected override void OnMouseLeave(EventArgs e) {
base.OnMouseLeave(e);
HotTabIndex = -1;
}
protected override void OnMouseMove(MouseEventArgs e) {
cancelSelecting = false; // must set to false here so that drag tab works correctly
base.OnMouseMove(e);
//// TCM_HITTEST checks to see if the mouse is over a new tab
TCHITTESTINFO HTI = new TCHITTESTINFO(e.X, e.Y);
HotTabIndex = SendMessage(this.Handle, TCM_HITTEST, IntPtr.Zero, ref HTI);
if (e.Button != MouseButtons.Left || dragTab2 == null) // not dragging
return;
// we have to do this because we don't select on click
// if this isn't done then sometimes a sloppy click (click with small drag) wouldn't select the tab
this.SelectedTab = dragTab2;
this.DoDragDrop(dragTab2, DragDropEffects.All); // start drag
}
protected override void OnDragOver(DragEventArgs e) {
base.OnDragOver(e);
TabControl tc = this;
// a tab is dragged?
if (e.Data.GetData(typeof(TabPage)) == null)
return;
TabPage dragTab = (TabPage) e.Data.GetData(typeof(TabPage));
TabControl tc2 = dragTab.Parent as TabControl;
if (tc2 == null)
return;
int dragTab_index = tc.TabPages.IndexOf(dragTab);
// hover over a tab?
int hoverTab_index = getHoverTabIndex(tc);
if (hoverTab_index < 0) {
e.Effect = DragDropEffects.None;
return;
}
TabPage hoverTab = tc.TabPages[hoverTab_index];
e.Effect = DragDropEffects.Move;
if (dragTab == hoverTab)
return;
// swap dragTab & hoverTab - avoids toggeling
Rectangle dragTabRect = tc.GetTabRect(dragTab_index);
Rectangle hoverTabRect = tc.GetTabRect(hoverTab_index);
if (dragTabRect.Width < hoverTabRect.Width) {
Point tcLocation = tc.PointToScreen(tc.Location);
if (dragTab_index < hoverTab_index) {
if ((e.X - tcLocation.X) > ((hoverTabRect.X + hoverTabRect.Width) - dragTabRect.Width))
swapTabPages(tc, dragTab, hoverTab);
} else if (dragTab_index > hoverTab_index) {
if ((e.X - tcLocation.X) < (hoverTabRect.X + dragTabRect.Width))
swapTabPages(tc, dragTab, hoverTab);
}
}
else
swapTabPages(tc, dragTab, hoverTab);
// select new pos of dragTab
tc.SelectedIndex = tc.TabPages.IndexOf(dragTab);
}
private static int getHoverTabIndex(TabControl tc) {
Point xy = tc.PointToClient(Cursor.Position);
for (int i = 0; i < tc.TabPages.Count; i++) {
if (tc.GetTabRect(i).Contains(xy))
return i;
}
return -1;
}
private static void swapTabPages(TabControl tc, TabPage src, TabPage dst) {
int index_src = tc.TabPages.IndexOf(src);
int index_dst = tc.TabPages.IndexOf(dst);
tc.TabPages[index_dst] = src;
tc.TabPages[index_src] = dst;
tc.Refresh();
}
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hwnd, int msg, IntPtr wParam, ref TCHITTESTINFO lParam);
[StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct TCHITTESTINFO {
public Point pt;
public TCHITTESTFLAGS flags;
public TCHITTESTINFO(int x, int y) {
pt = new Point(x, y);
flags = TCHITTESTFLAGS.TCHT_NOWHERE;
}
}
[Flags]
private enum TCHITTESTFLAGS {
TCHT_NOWHERE = 1,
TCHT_ONITEMICON = 2,
TCHT_ONITEMLABEL = 4,
TCHT_ONITEM = TCHT_ONITEMICON | TCHT_ONITEMLABEL
}
private const int TCM_FIRST = 0x1300;
private const int TCM_HITTEST = TCM_FIRST + 13;
}
}
/*
Call Stack when hang:
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.MessageBox.ShowCore(System.Windows.Forms.IWin32Window owner, string text, string caption, System.Windows.Forms.MessageBoxButtons buttons, System.Windows.Forms.MessageBoxIcon icon, System.Windows.Forms.MessageBoxDefaultButton defaultButton, System.Windows.Forms.MessageBoxOptions options, bool showHelp) + 0x44a bytes
System.Windows.Forms.dll!System.Windows.Forms.MessageBox.Show(System.Windows.Forms.IWin32Window owner, string text) + 0x3a bytes
> DynamicTests.exe!DynamicTests.Form2.tc_SelectedIndexChanged.AnonymousMethod__1() Line 28 + 0x16 bytes C#
[Native to Managed Transition]
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args) + 0x6a bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme) + 0x97 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(object obj) + 0xef bytes
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x285 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x9 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x57 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme) + 0x113 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks() + 0x10c bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x2b8 bytes
System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) + 0x1ee bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x145 bytes
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Control.DoDragDrop(object data, System.Windows.Forms.DragDropEffects allowedEffects) + 0x187 bytes
DynamicTests.exe!DynamicTests.TabControl2.OnMouseMove(System.Windows.Forms.MouseEventArgs e) Line 97 + 0x20 bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseMove(ref System.Windows.Forms.Message m) + 0x89 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0xe64 bytes
System.Windows.Forms.dll!System.Windows.Forms.TabControl.WndProc(ref System.Windows.Forms.Message m) + 0x416 bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x145 bytes
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason, int pvLoopData) + 0x681 bytes
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason, System.Windows.Forms.ApplicationContext context) + 0x56f bytes
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) + 0x6f bytes
DynamicTests.exe!DynamicTests.Program.Main() Line 149 + 0x2a bytes C#
*/