2017-01-25 2 views
0

Was ist der beste Weg, Befehle in WPF zu verwenden?Verwenden Sie ICommand in WPF

Ich benutze einige Befehle, diese Befehle können eine Weile dauern. Ich möchte, dass meine Anwendung während der Ausführung nicht einfriert, aber ich möchte, dass die Funktionen deaktiviert werden.

gibt es mein MainWindow.xaml:

<Window ...> 
    <Window.DataContext> 
     <local:MainViewModel/> 
    </Window.DataContext> 
    <Grid>   
     <Button Grid.Row="0" 
       Grid.Column="0" 
       Style="{StaticResource StyleButton}" 
       Content="Load" 
       Command="{Binding LoadCommand}"/> 
     <Button Grid.Row="0" 
       Grid.Column="1" 
       Style="{StaticResource StyleButton}" 
       Content="Generate" 
       Command="{Binding GenerateCommand}"/> 
    </Grid> 
</Window> 

und meine MainViewModel.cs:

public class MainViewModel : ViewModelBase 
{ 

    #region GenerateCommand 
    #endregion 

    #region Load command 
    private ICommand _loadCommand; 
    public ICommand LoadCommand 
    { 
     get 
     { 
      if (_loadCommand == null) 
       _loadCommand = new RelayCommand(OnLoad, CanLoad); 
      return _loadCommand; 
     } 
    } 

    private void OnLoad() 
    { 
     //My code 
    } 
    private bool CanLoad() 
    { 
     return true; 
    } 
    #endregion 
} 

sah ich eine Lösung mit Hintergrund Arbeiter, aber ich weiß nicht, wie es zu benutzen. Und ich frage mich, ob ich eine Instanz per Befehl erstellen soll.

Gibt es einen saubereren/besten Weg?

+0

haben einen Blick: http://stackoverflow.com/questions/30740642/asynchronous-mvvm-commands –

Antwort

2

Ich möchte, dass meine Anwendung nicht beim Laufen einfrieren, aber ich möchte die Funktionen deaktiviert werden.

Der Schlüssel zum Verhindern des Einfrierens der Anwendung besteht darin, einen lang andauernden Vorgang in einem Hintergrundthread auszuführen. Der einfachste Weg, dies zu tun, ist eine Aufgabe zu starten. Um das Fenster zu deaktivieren, können Sie die IsEnabled-Eigenschaft an eine Quelleigenschaft des Ansichtsmodells binden, das Sie vor dem Starten der Aufgabe festgelegt haben. Der folgende Beispielcode sollten Sie die Idee geben:

public class MainViewModel : ViewModelBase 
{ 
    private RelayCommand _loadCommand; 
    public ICommand LoadCommand 
    { 
     get 
     { 
      if (_loadCommand == null) 
       _loadCommand = new RelayCommand(OnLoad, CanLoad); 
      return _loadCommand; 
     } 
    } 

    private void OnLoad() 
    { 
     IsEnabled = false; 
     _canLoad = false; 
     _loadCommand.RaiseCanExecuteChanged(); 

     Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); }) //simulate som long-running operation that runs on a background thread... 
      .ContinueWith(task => 
      { 
       //reset the properties back on the UI thread once the task has finished 
       IsEnabled = true; 
       _canLoad = true; 
      }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 

    private bool _canLoad = true; 
    private bool CanLoad() 
    { 
     return _canLoad; 
    } 

    private bool _isEnabled; 
    public bool IsEnabled 
    { 
     get { return _isEnabled; } 
     set { _isEnabled = value; RaisePropertyChanged(); } 
    } 
} 

Beachten Sie, dass Sie keine UI-Element aus einem Hintergrund-Thread zugreifen können, da Kontrollen Gewinde Affinität: http://volatileread.com/Thread/Index?id=1056

+0

Ihre Lösung scheint die einfachste zu sein. Ich muss nur eine Aufgabe für jeden Befehl erstellen, oder? Ich habe gerade ein Problem mit 'CanLoad()', die Schaltfläche reaktiviert nicht, wenn ich die 'IsEnabled = true; _canLoad = true; ', aber ich werde eine Lösung finden. Danke –

+0

Ja, Sie erstellen eine neue Aufgabe für jeden Aufruf einer lang laufenden Hintergrundoperation. Der Aufruf der RaiseCanExecuteChanged() -Methode des Befehls sollte dazu führen, dass der CanLoad() - Delegat erneut aufgerufen wird und der Status des Befehls aktualisiert wird. – mm8

+0

Ich habe keine Definition für 'RaisePropertyChanged()'. Ich verwende eine ViewModelBase mit nur 'OnPropertyChanged' wie [diese] (http://stackoverflow.com/questions/1315621/implementing-otify-propertychanged-does-a-better-way-exist) –

1

Ich würde vorschlagen, Akka.Net zu verwenden: Sie können ein Beispiel mit WPF auf github finden.

Ich habe forked es zu stoppen und Befehle zu starten: mein Ziel war es, bidirektionale Kommunikation zwischen Akka.Net Akteure und ViewModel zu zeigen.

Sie finden das Viewmodel die ActorSystem Aufruf wie diese

private void StartCpuMethod() { 
     Debug.WriteLine("StartCpuMethod"); 
     ActorSystemReference.Start(); 
    } 
    private void StopCpuMethod() { 
     Debug.WriteLine("StopCpuMethod"); 
     ActorSystemReference.Stop(); 
    } 

mit einem Schauspieler diese Nachrichten

public CPUReadActor() 
    { 
     Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage()); 
     Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg)); 
    } 

    private void ReceiveSyncMessage(ReadCPUSyncMessage msg) 
    { 
     switch (msg.Op) 
     { 
      case SyncOp.Start: 
       OnCommandStart(); 
       break; 
      case SyncOp.Stop: 
       OnCommandStop(); 
       break; 
      default: 
       throw new Exception("unknown Op " + msg.Op.ToString()); 
     } 
    } 

und umgekehrt von einem Schauspieler Empfang

public ChartingActor(Action<float, DateTime> dataPointSetter) 
    { 
     this._dataPointSetter = dataPointSetter; 

     Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg)); 
    } 

    private void ReceiveDrawPointMessage(DrawPointMessage msg) 
    { 
     _dataPointSetter(msg.Value, msg.Date); 
    } 

zum ViewModel

public MainWindowViewModel() 
    { 
     StartCpuCommand = new RelayCommand(StartCpuMethod); 
     StopCpuCommand = new RelayCommand(StopCpuMethod); 

     SetupChartModel(); 
     Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d)); 

     ActorSystemReference.CreateActorSystem(dataPointSetter); 
    } 

    private void SetDataPoint(float value, DateTime date) 
    { 
     CurrentValue = value; 
     UpdateLineSeries(value, date); 
    } 
2

Mein Ansatz UI Einfrieren in diesen Szenarien zu vermeiden, ist Verwenden Sie in der ICommand-Ausführung async/awa, und führen Sie den lang laufenden Code in einem Hintergrundthread aus. Ihr modifizierten Code würde wie folgt aussehen:

public ICommand LoadCommand 
{ 
    get 
    { 
     if (_loadCommand == null) 
      _loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad); 
     return _loadCommand; 
    } 
} 

private async Task OnLoadAsync() 
{ 
    await Task.Run(() => MyLongRunningProcess()); 
} 

Wenn das Hintergrundaufgabe etwas an die UI gebunden aktualisieren muss, dann muss es in einem Dispatcher.Invoke gewickelt werden (oder Dispatcher.BeginInvoke).

Wenn Sie verhindern möchten, dass der Befehl ein zweites Mal ausgeführt wird, setzen Sie "CanLoad" vor der Zeile await Task.Run(... auf "True" und danach auf "false".

+0

Diese Art zu schreiben ist wie die Antwort von mm8? Gibt es einen Unterschied in der Ausführung? Das ist übrigens noch einfacher zu schreiben. –

+0

@ A.Pissicat 'Task.Run()' ist wirklich nur eine prägnante Version des Schreibens von 'Task.Factory.StartNew ...', eingeführt in .Net 4.5. Die 'async/await'-Schlüsselwörter sind auch sehr elegant zu verwenden, sobald Sie verstehen, wie sie funktionieren. In einfachen Worten kehrt der UI-Thread zu dem zurück, was er getan hat, nachdem er diese Aufgabe gestartet hat. Wenn diese Aufgabe abgeschlossen ist, wird der UI-Thread "dort weitermachen, wo er aufgehört hat" und den verbleibenden Code ausführen, der nach dieser Zeile kommt (was mehr "wartbare" Aufrufe sein können). –

0

Der beste Weg hier ist eine Verwendung von async/erwarten, meiner Meinung nach. https://msdn.microsoft.com/ru-ru/library/mt674882.aspx

public class MainViewModel : ViewModelBase 
{ 

    public MainViewModel() 
    { 
     LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad); 
    } 

    public ICommand LoadCommand { get; } 

    private async void OnLoadAync() 
    { 
     await SomethingAwaitable(); 
    } 

    private Task<bool> SomethingAwaitable() 
    { 
     //Your code 
    } 

} 
Verwandte Themen