4

我有一个带有状态栏的 WPF 应用程序。

<StatusBar Grid.Row="1"
           Height="23"
           Name="StatusBar1"
           VerticalAlignment="Bottom">
    <TextBlock Name="TextBlockStatus" />
</StatusBar>

当我做少量工作时,我想在那里显示文本并切换到沙漏等待光标。

此代码将更新光标,但状态栏文本不会更新...

Cursor = Cursors.Wait
TextBlockStatus.Text = "Loading..."
System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
TextBlockStatus.Text = String.Empty
Cursor = Cursors.Arrow

更新

亚历山德拉回答的启发...

如果我这样做,它会起作用,但我对这个解决方案一点也不满意。有没有更简单的方法?

Delegate Sub Load1()
Sub Load2()
    System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
End Sub
Dim Load3 As Load1 = AddressOf Load2

Sub Load()
    Cursor = Cursors.Wait
    TextBlockStatus.Text = "Loading..."
    Dispatcher.Invoke(DispatcherPriority.Background, Load3)
    TextBlockStatus.Text = String.Empty
    Cursor = Cursors.Arrow
End Sub

我宁愿它看起来像这样......

Sub Load()
    Cursor = Cursors.Wait
    TextBlockStatus.Text = "Loading..."

    'somehow put all the Dispatcher, Invoke, Delegate,
     AddressOf, and method definition stuff here'

    TextBlockStatus.Text = String.Empty
    Cursor = Cursors.Arrow
End Sub

或者甚至更好...

Sub Load()
    Cursor = Cursors.Wait
    ForceStatus("Loading...")
    System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
    ForceStatus(String.Empty)
    Cursor = Cursors.Arrow
End Sub

Sub ForceStatus(ByVal Text As String)
    TextBlockStatus.Text = Text
    'perform magic'
End Sub

更新

我还尝试将 TextBlock 绑定到公共属性并按照IanGilham 的建议实施INotifyPropertyChanged。这不起作用

XAML:

<TextBlock Text="{Binding Path=StatusText}"/>

视觉基础:

Imports System.ComponentModel
Partial Public Class Window1
    Implements INotifyPropertyChanged

    Private _StatusText As String = String.Empty
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Property StatusText() As String
        Get
            Return _StatusText
        End Get
        Set(ByVal value As String)
            _StatusText = value
            OnPropertyChanged("StatusText")
        End Set
    End Property

    Shadows Sub OnPropertyChanged(ByVal name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
    ...

    Sub Load()
        ...
        Cursor = Cursors.Wait
        StatusText = "Loading..."
        System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
        StatusText = String.Empty
        Cursor = Cursors.Arrow
        ...
    End Sub
...
4

7 回答 7

6

您应该使用BackgroundWorker。这项工作将在一个单独的线程中进行,这意味着您的 UI 线程将是免费的,您的应用程序仍将是响应式的。

它不会是一个非常紧凑的代码解决方案,但它对您的用户来说是最强大和最友好的。

于 2009-04-23T17:53:58.243 回答
1

您可以轻松调用

TextBlockStatus.UpdateLayout();

在您更改应该刷新控件并更改屏幕上的文本的 Text 属性之后。

我也用

this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate {
    /* your code here */
}));

(尝试)确保我的任务在刷新完成后运行。

我不得不承认,它在大约 90% - 95% 的时间里都可以工作(有时文本只有在任务完成后才会更改,或者会稍有延迟),但我找不到更好的东西。

编辑问题的编辑:

我不是 VB 方面的专家,但如果它不支持匿名内联方法,那么您的第二种方法应该可行。UpdateLayout()在调用调度程序之前尝试调用

Cursor = Cursors.Wait
TextBlockStatus.Text = "Loading..."
TextBlockStatus.UpdateLayout(); //include the update before calling dispatcher
Dispatcher.Invoke(DispatcherPriority.Background, Load3)
TextBlockStatus.Text = String.Empty
Cursor = Cursors.Arrow
于 2009-04-23T16:05:18.673 回答
1

用户Ray在另一个问题中提交了解决此问题的答案他的回答基于Shaun Bowe在第三个问题中的回答。

这是我的实现...

Sub UpdateStatus(ByVal Message As String)
    If Message = String.Empty Then
        Cursor = Cursors.Arrow
    Else
        Cursor = Cursors.Wait
    End If
    TextBlockStatus.Text = Message
    AllowUIToUpdate()
End Sub

Public Sub AllowUIToUpdate()
    Dim frame As New DispatcherFrame()
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, New DispatcherOperationCallback(AddressOf JunkMethod), frame)
    Dispatcher.PushFrame(frame)
End Sub

Private Function JunkMethod(ByVal arg As Object) As Object
    DirectCast(arg, DispatcherFrame).Continue = False
    Return Nothing
End Function

根据IanGilham建议,将它与 XAML 绑定和 INotifyPropertyChanged 结合起来可能会更好。

于 2009-04-24T13:10:21.113 回答
1

这是我的代码(状态是带有 x:Name="Status" 的 WPF 文本块)

(它是 c# 但我认为它应该可以移植到 VB...)

    private void Button_Click(object sender, RoutedEventArgs e) {
        var bw = new BackgroundWorker();
        bw.DoWork += DoSomething;
        bw.RunWorkerAsync();
    }

    private void DoSomething(object sender, DoWorkEventArgs args) {
        UpdateStatus("Doing Something...");

        ...

        UpdateStatus("Done...");
    }

    private void UpdateStatus(string text) {
        Status.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => SetStatus(text)));
    }

    private void SetStatus(string text) {
        Status.Text = text;
    }
于 2010-06-08T15:26:35.667 回答
1

这很好用。但请注意 :DispatcherPriority.Render"

private static Action EmptyDelegate = delegate() { };
public void RefreshUI(UIElement uiElement)
{
    uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
public string StatusBarString
{
    set 
    { 
        statusBar.Content = _satusBarString;
        RefreshUI(statusBar);
    }
}
于 2012-02-21T11:38:52.423 回答
1

你试过这样吗?

TextBlockStatus.Dispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => { TextBlockStatus.Text = message; }));

我遇到了同样的问题,出于某种原因,使用 DispatcherPriority.Loaded 对我有用。此外,不需要 UpdateLayout。

我希望它也对你有用,干杯,

安德烈

于 2010-08-26T19:35:26.647 回答
0

我将创建一个公共属性来保存文本字段并实现INotifyPropertyChanged。您可以轻松地将 TextBlock绑定到 xaml 文件中的属性。

<TextBlock Text="{Binding Path=StatusText}"/>

然后,您只需更改属性,UI 将始终是最新的。

在我目前的工作项目中,代表 Window 的类只包含方法处理程序,它们调用 ViewModel/Presenter 类的公共接口。ViewModel 包含 UI 需要绑定的任何内容,并通过将作业添加到 ThreadPool 来启动任何和所有功能。

当然,我目前所有的项目都涉及批处理,所以让 ThreadPool 处理并发问题对我来说是有意义的。

更新:您能否再次尝试使用属性方法,但要确保该属性是公开的。根据此页面,您只能绑定到公共属性。

Imports System.ComponentModel
Partial Public Class Window1
    Implements INotifyPropertyChanged

    Private _StatusText As String = String.Empty
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    // I think the property has to be public for binding to work
    Public Property StatusText() As String
        Get
            Return _StatusText
        End Get
        Set(ByVal value As String)
            _StatusText = value
            OnPropertyChanged("StatusText")
        End Set
    End Property

    Shadows Sub OnPropertyChanged(ByVal name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
    ...

    Sub Load()
        ...
        Cursor = Cursors.Wait
        StatusText = "Loading..."
        System.Threading.Thread.Sleep(New TimeSpan(0, 0, 3))
        StatusText = String.Empty
        Cursor = Cursors.Arrow
        ...
    End Sub

...

于 2009-04-23T18:28:02.763 回答