Могу ли я обновить текст WPF StatusBar, прежде чем заставлять пользователя ждать? - PullRequest
4 голосов
/ 23 апреля 2009

У меня есть приложение 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

Обновление

Вдохновлен Александрой ответ ...

Это работает , если я делаю это таким образом, но я совсем не доволен этим решением. Есть ли более простой способ?

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

Ответы [ 7 ]

5 голосов
/ 23 апреля 2009

Вы должны использовать BackgroundWorker . Работа будет проходить в отдельном потоке, что означает, что ваш поток пользовательского интерфейса будет свободным, а ваше приложение по-прежнему будет отзывчивым.

Это не будет невероятно компактное решение с точки зрения кода, но оно будет самым надежным и удобным для ваших пользователей.

1 голос
/ 21 февраля 2012

Это хорошо работает. но обратите внимание на: 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 голос
/ 26 августа 2010

Ты пробовал вот так?

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

У меня была такая же проблема, и по какой-то причине использование DispatcherPriority.Loaded работало на меня. Также нет необходимости в UpdateLayout.

Я надеюсь, что это работает и для вас, ура,

Андре

1 голос
/ 08 июня 2010

вот мой код (Status - это текстовый блок WPF с x: Name = "Status")

(Это 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 голос
/ 24 апреля 2009

Пользователь Луч отправил ответ, который решает эту проблему в другом вопросе. Его ответ основан на Шон Боу ответ в третьем вопросе.

Это моя реализация ...

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

Возможно, было бы неплохо объединить это с привязкой XAML и INotifyPropertyChanged для IanGilham предложение .

1 голос
/ 23 апреля 2009

Вы можете легко позвонить

TextBlockStatus.UpdateLayout();

сразу после изменения свойства Text, которое должно обновить элемент управления и изменить текст на экране.

Я также использую

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

чтобы (попытаться) убедиться, что моя задача запускается после обновления.

Я должен признать, что это работает ~ 90% - 95% времени (бывают случаи, когда текст изменяется только после того, как задание выполнено или изменяется с небольшой задержкой), но я не мог найти ничего лучше. 1011 *

РЕДАКТИРОВАТЬ для редактирования вопроса:

Я не эксперт в 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
0 голосов
/ 23 апреля 2009

Я бы создал public свойство для хранения текстового поля и реализации INotifyPropertyChanged . Вы можете легко связать TextBlock со свойством в файле xaml.

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

Тогда вы просто меняете свойство, и пользовательский интерфейс всегда будет обновлен.

В моих рабочих проектах на данный момент класс, представляющий Window, содержит только обработчики методов, которые вызывают открытый интерфейс класса ViewModel / Presenter. ViewModel содержит все, что нужно привязать пользовательскому интерфейсу, и запускает все функции, добавляя задания в 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

...

...