前言:我知道这是一种不寻常/不正确的方法。我可以用“真正的”ShowDialog(),后台工作者/线程等来做到这一点。我不是那样寻求帮助的;我正在努力做我在这里描述的具体内容,即使它很难看。如果出于X原因这是不可能的,请告诉我。
我为一些长时间运行的操作创建了一个精彩的进度对话框。我需要在新线程上显示此对话框,同时在调用(大多数情况下为UI)线程上继续处理。
这有三个真正的要求:
我看起来像这样(到目前为止工作得很好,除了上面的那些问题,运行除外):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
我尝试了几种方法:
关于如何使这项工作的任何线索或智慧?
答案 0 :(得分:6)
在(死)表单上始终显示进度窗口是困难的要求。这通常通过使用Form.Show(所有者)重载来处理。它会给你的情况带来麻烦,WF不会欣赏属于另一个线程的所有者表单。这可以通过P / Invoking SetWindowLong()来设置所有者。
但是现在出现了一个新问题,一旦尝试向其所有者发送消息,进度窗口就会消失。有点令人惊讶的是,当您使用Invoke()而不是BeginInvoke()来更新进度时,这个问题有点消失。有点,您仍然可以通过将鼠标移动到禁用所有者的边框上来解决问题。实际上,你必须使用TopMost来确定Z顺序。更现实的是,Windows不支持您尝试做的事情。你知道真正的解决方案,它是你问题的首要问题。
这是一些可以试验的代码。它假定你进步形式称为dlgProgress:
Imports System.Threading
Public Class ShowProgress
Implements IDisposable
Private Delegate Sub UpdateProgressDelegate(ByVal pct As Integer)
Private mOwnerHandle As IntPtr
Private mOwnerRect As Rectangle
Private mProgress As dlgProgress
Private mInterlock As ManualResetEvent
Public Sub New(ByVal owner As Form)
Debug.Assert(owner.Created)
mOwnerHandle = owner.Handle
mOwnerRect = owner.Bounds
mInterlock = New ManualResetEvent(False)
Dim t As Thread = New Thread(AddressOf dlgStart)
t.SetApartmentState(ApartmentState.STA)
t.Start()
mInterlock.WaitOne()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
mProgress.BeginInvoke(New MethodInvoker(AddressOf dlgClose))
End Sub
Public Sub UpdateProgress(ByVal pct As Integer)
mProgress.Invoke(New UpdateProgressDelegate(AddressOf dlgUpdate), pct)
End Sub
Private Sub dlgStart()
mProgress = New dlgProgress
mProgress.StartPosition = FormStartPosition.Manual
mProgress.ShowInTaskbar = False
AddHandler mProgress.Load, AddressOf dlgLoad
AddHandler mProgress.FormClosing, AddressOf dlgClosing
EnableWindow(mOwnerHandle, False)
SetWindowLong(mProgress.Handle, -8, mOwnerHandle)
Application.Run(mProgress)
End Sub
Private Sub dlgLoad(ByVal sender As Object, ByVal e As EventArgs)
mProgress.Location = New Point( _
mOwnerRect.Left + (mOwnerRect.Width - mProgress.Width) \ 2, _
mOwnerRect.Top + (mOwnerRect.Height - mProgress.Height) \ 2)
mInterlock.Set()
End Sub
Private Sub dlgUpdate(ByVal pct As Integer)
mProgress.ProgressBar1.Value = pct
End Sub
Private Sub dlgClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)
EnableWindow(mOwnerHandle, True)
End Sub
Private Sub dlgClose()
mProgress.Close()
mProgress = Nothing
End Sub
'--- P/Invoke
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
If IntPtr.Size = 4 Then
Return SetWindowLongPtr32(hWnd, nIndex, dwNewLong)
Else
Return SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
End If
End Function
Private Declare Function EnableWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal enabled As Boolean) As Boolean
Private Declare Function SetWindowLongPtr32 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
Private Declare Function SetWindowLongPtr64 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
End Class
样本用法:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Using dlg As New ShowProgress(Me)
For ix As Integer = 1 To 100
dlg.UpdateProgress(ix)
System.Threading.Thread.Sleep(50)
Next
End Using
End Sub
答案 1 :(得分:1)
我刚才写了a blog post on this topic(处理启动表格,但想法是一样的)。代码在C#中,但我会尝试将其转换为此处的帖子(即将...)。
答案 2 :(得分:1)
我知道它有点脏,但你不能只在对话框中完成工作吗?
我的意思是
Dialog.MyShowDialog(callback);
并在回调和UI更新中完成所有工作。
这样你就可以保留ShowDialog行为,同时允许调用不同的代码。