我能在让用户等待之前更新WPF StatusBar文本吗?

4

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

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

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

这段代码将更新光标,但StatusBar的文本不会更新...

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

更新

Alexandra回答启发...

如果我这样做,它可以工作,但我对这个解决方案并不满意。有更简单的方法吗?

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绑定到公共属性,并实现INotifyPropertyChanged,如IanGilham建议的那样。但是无法正常工作

XAML:

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

Visual Basic:

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
...

1
在这种情况下,我会小心使用 Dispatcher.Invoke。你基本上依赖于Invoke的副作用来更新UI。它可以工作,但它不是它总是工作的契约的一部分。请注意,您可以将 Sleep 调用移出 Invoke 方法(在调用 Invoke 后放置它)。只要您调用 Invoke(具有足够高的优先级以立即完成工作)并且执行任何操作,UI 就会得到更新。 - MichaC
7个回答

6

你应该使用BackgroundWorker。工作将在一个单独的线程上进行,这意味着你的UI线程将是空闲的,你的应用程序仍然会响应。

从代码方面来看,它不会是一个非常紧凑的解决方案,但它是最健壮和用户友好的。


像这样吗?http://stackoverflow.com/questions/783649/is-there-no-simple-way-to-set-wpf-statusbar-text - Zack Peterson

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);
    }
}

1

这是我的代码(状态是一个带有x:Name="Status"的WPF TextBlock)

(虽然是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;
    }

1

你试过这样做吗?

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

我曾经遇到过同样的问题,但奇怪的是使用 DispatcherPriority.Loaded 对我很有效。而且,也不需要 UpdateLayout。

希望这对你也有用,祝好运,

André


1

你可以轻松调用

TextBlockStatus.UpdateLayout();

当你改变文本属性后,应该刷新控件并更改屏幕上的文本。

我也使用

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

请查看我的有关Dispatcher的更新。我认为VB不支持语句体内的匿名方法。 - Zack Peterson

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相结合可能会更好。


1
好贴+1。我是一个WPF新手,我的状态栏文本也没有更新。我觉得告诉用户为每件事都开启一个新线程很糟糕。我正在尝试这些解决方案... - Steve

0
我会创建一个“public”属性来保存文本字段并实现INotifyPropertyChanged。您可以在XAML文件中轻松绑定TextBlock到属性上。
<TextBlock Text="{Binding Path=StatusText}"/>

然后你只需要更改属性,UI 就会一直保持最新。

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

当然,我目前的所有项目都涉及批处理,因此让 ThreadPool 处理并发问题在我的情况下是有意义的。

更新:您可以再次尝试属性方法,但请确保该属性为公共属性。根据 this page,您只能绑定公共属性。

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

...


使用绑定和MVVM正如您建议的那样是一个好主意,但它并不能解决问题。 - Torben Junker Kjær

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接