2013-12-22 2 views
3

Это, наверное, действительно основной вопрос, пожалуйста, несите меня, я все еще очень новичок в мире WPF/C#.Динамическое обновление окна WPF до того, как код завершит выполнение

У меня есть приложение WPF, где я открываю новое окно, если нажата кнопка.

Окно называется Sync, и все, что он делает, создает экземпляр класса viewmodel, который содержит некоторые общедоступные свойства, привязанные к моему представлению.

Viewmodel также создает класс, содержащий много бизнес-логики, это обновляет связанные свойства ViewModel, целью которого является обновление содержимого моего окна.

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

Я думаю, что мне не хватает чего-то довольно элементарного здесь. Как получить мое окно для мгновенной загрузки, а затем обновить представление, если какое-либо из свойств изменилось? Должен ли я прослушивать событие PropertyChanged, а затем обновлять представление? Где я это делаю? Внутри сеттера представления модели?

Вот несколько упрощенный код:

Вызов мое окно из моего главного окна View Model

public void SyncAction() 
    { 
     Sync syncWindow = new Sync(); 
     syncWindow.Show(); 
     syncWindow.Activate(); 
    } 

Окно

public partial class Sync : Window 
{ 
    public Sync() 
    { 
     InitializeComponent(); 

     var viewModel = new SyncViewModel(); 
    } 
} 

вида модели

class SyncViewModel 
{ 

    private string _miscStatus = ""; 

    public SyncViewModel() 
    { 
     var sync = new SyncLogic(); 
     sync.SyncAll(this); 
    } 

    public string MiscStatus 
    { 
     get 
     { 
      return _miscStatus; 
     } 
     set 
     { 
      _miscStatus += value; 
     } 
    } 
} 

Некоторые Бусин ESS логика

class SyncLogic 
{ 
    private ViewModel.SyncViewModel _syncViewModel; 

    public void SyncAll(ViewModel.SyncViewModel syncViewModel) 
    { 
     _syncViewModel = syncViewModel; 

     // lock our synctime 
     var syncTime = DateTools.getNow(); 

     _syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString(); 

     // Do lots of other stuff 

     _syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
      DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString()); 
    } 
} 

Бонус вопрос: Как я буду обновлять вид изнутри моей бизнес-логики (передав ссылку на ViewModel и обновляет свои свойства оттуда) кажется немного запутано. Я определенно хочу, чтобы бизнес-логика была отдельной, но я не уверен, как я могу передать любой выход обратно в viewmodel. Какой был бы лучший способ сделать это, пожалуйста?

ответ

1

Что бы я сделал:

Не делать каких-либо тяжелых логики в конструкторе ViewModel. Конструктор должен только инициализировать объект и ничего не делать. В вашем примере конструктор должен быть пустым.

public SyncViewModel() 
{  
} 

SyncLogic не должны быть осведомлены о ViewModel. Представьте другой класс для передачи входных аргументов и результатов синхронизации. Скажем SyncArguments и SyncResult.

class SyncLogic 
{ 
    public SyncResult SyncAll(SyncArguments syncArgs) 
    { 
     var syncResult = new SyncResult(); 

     // Do lots of other stuff 
     // populate syncResult 
     return syncResult; 
    } 
} 

Внедрение метода в ViewModel, который должен быть вызван, чтобы сделать «синхронизации» логику, и сделать этот метод async. Таким образом, очень легко сделать тяжелый материал в фоновом режиме и оставить поток пользовательского интерфейса выполнять работу, которую он должен выполнять, нарисовать интерфейс.

public async Task Sync() 
{ 
    // lock our synctime 
    var syncTime = DateTools.getNow(); 
    MiscStatus = "Sync starting at " + syncTime.ToString(); 


    var sync = new SyncLogic(); 
    var syncArgs = new SyncArguments(); 
    //populate syncArgs from ViewModel data 

    //call the SyncAll as new Task so it will be executed as background operation 
    //and "await" the result 
    var syncResults = await Task.Factory.StartNew(()=>sync.SyncAll(syncArgs)); 

    //when the Task completes your execution will continue here and you can populate the 
    //ViewModel with results 

    MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
     DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString()); 

} 

сделать обработчик события нажатия кнопки, которая создает и показывает окно async, так что вы можете вызвать метод Sync на ViewModel

private void async Button_click(object sender, EventArgs e) 
{ 
    Sync syncWindow = new Sync(); 
    var viewModel = new SyncViewModel(); 
    syncWindow.DataContext = viewModel; 
    syncWindow.Show(); 
    syncWindow.Activate(); 
    await viewModel.Sync(); 
} 

Это потянет окно, не дожидаясь от способа синхронизации. Когда Sync taks завершится, свойства viewmodel будут заполнены из SyncResult, а привязки будут рисовать их на экране.

Надеюсь, у вас возникла идея, извините, если в моем коде есть некоторые ошибки, не уверен, что все это скомпилировано.

+0

Все ответы на мой вопрос были очень полезными и позволили мне увеличить мое понимание, однако это дало мне самое большое начало, и теперь я успешно реализовал нечто похожее, которое: а) работает так, как я предполагал, и б) гораздо более ремонтопригодным. Спасибо @jure –

1

Во-первых, убедитесь, чтобы установить ViewModel, как DataContext отображения вида:

public partial class Sync : Window 
{ 
    public Sync() 
    { 
     InitializeComponent(); 

     var viewModel = new SyncViewModel(); 
     DataContext = viewModel; 
    } 
} 

Во-вторых, вы должны запустить «синхронизации» вещи в фоновом потоке. Это проще всего с асинхронным + ждет ключевые слов в .NET 4.5:

public async void SyncAll(ViewModel.SyncViewModel syncViewModel) 
{ 
    _syncViewModel = syncViewModel; 

    // lock our synctime 
    var syncTime = DateTools.getNow(); 

    _syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString(); 

    await Task.Factory.StartNew(() => { 
     // Do lots of other stuff 
    }); 

    _syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
     DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString()); 
} 
1

С привязкой данных вашего окна будет автоматически обновляться до тех пор, как он уведомил, что свойства его привязанные к были изменены. Итак, вам нужно реализовать INotifyPropertyChanged в viewmodel и вызывать событие с изменением свойства при изменении значения свойства источника привязки. Например:

public class SyncViewModel : INotifyPropertyChanged 
{ 
    private string _miscStatus = ""; 
    public string MiscStatus 
    { 
     get{ return _miscStatus; } 
     set 
     { 
      _miscStatus += value; 
      OnPropertyChanged("MiscStatus"); 
     } 
    } 

    #region INotifyPropertyChanged implementation 
    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    #endregion 
} 
+0

Спасибо за очень сжатое объяснение того, как реализовать INotifyPropertyChanged, очень полезно, я был почти там, но не был уверен, как вызвать измененное свойство. Теперь мне нужно реализовать предложения «async», чтобы сразу открыть окно. –

2

Почему бы вам уход принимает ли обновление визуального эффекта до или после того, как код завершения выполнения? Внутренние свойства обновляются немедленно; любой код, запрашивающий пользовательский интерфейс, увидит новые значения.

Единственный раз, когда пользователь сможет ощутить разницу между обновлением во исполнении против после исполнения, если у вас есть затянувшееся вычисление в потоке пользовательского интерфейса. Не делай этого.

Вместо этого запустите вычисление асинхронно с пользовательским интерфейсом, чтобы перерисовывать сообщения можно обрабатывать тем временем. Вы можете сделать это, используя фоновый поток, но новый более простой способ с C# 4 и более поздними версиями - async. Поскольку async реализован с использованием сообщений продолжения в потоке пользовательского интерфейса, вам не нужно синхронизировать доступ к данным или маршировать доступ к пользовательскому интерфейсу между потоками. Это просто работает, и очень хорошо. Единственное, что вам нужно сделать, это разбить свой код на достаточно маленькие куски, каждый из которых реализован как метод async, что вы не вызываете заметной задержки.

1

В случае, если кто-то еще сталкивается с этой проблемой в WPF, решение, описанное here, действительно прост и просто отлично работает для меня. Он использует метод расширения для принудительного UIElement быть вынесено:

public static class ExtensionMethods 
{ 

    private static Action EmptyDelegate = delegate() { }; 


    public static void Refresh(this UIElement uiElement) 
    { 
     uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); 
    } 
} 

Затем просто использовать как:

private void SomeLongOperation() 
{ 
    // long operations... 

    // UI update 
     label1.Content = someValue; 
     label1.Refresh(); 

    // continue long operations 
    } 
} 

Цитирование оригинального автора:

Метод Refresh является расширение метод, который принимает какой-либо элемент пользовательского интерфейса, а затем вызывает этот метод вызова UIElement Dispatcher's Invoke. Хитрость заключается в вызове метода Invoke с DispatcherPriority Render или ниже. Поскольку мы ничего не хотим делать, я создал пустой делегат. Итак, как получилось, что это обеспечивает функциональность обновления?

Если для параметра DispatcherPriority установлено значение Render (или ниже), , код выполнит все операции с этим приоритетом или выше. В этом примере код уже устанавливает label1.Content на что-то еще, что приведет к операции рендеринга. Таким образом, вызывая Dispatcher.Invoke, код в основном просит систему выполнить все операции, которые являются Render или более высоким приоритетом, таким образом, элемент управления затем сделает сам (рисунок нового контента). Затем он выполнит предоставленный делегат (который является нашим пустым методом).

Смежные вопросы