C# Im Command einen Text in Statusbar ausgeben

toma123

Banned
Registriert
Apr. 2020
Beiträge
100
Langsam bin ich immer mehr am verzweifeln. Folgendes möchte ich machen.

Ich möchte ein Command an einen Button binden. Mein Vorgehen bisher:

1. Ich habe eine Klasse (AppCommands.cs) erstellt, wo zuerst ein neues Command erzeuge:
Code:
 public static class AppCommands
    {
        public static ICommand Exit { get; set; } = new ExitCommand();
    }

2. Außerdem eine Klasse (ExitCommands.cs ) worin ich mein Command anlege:
Code:
 public class ExitCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            // Hier würde ich jetzt gern eine Ausgabe in der Statuszeile machen, bevor die App geschlossen wird
            Application.Current.Shutdown();
        }
    }

3. In meiner MainWindow.xaml bin ich das ganze an einen Button folgendermaßen:
Code:
<Button Command="{x:Static local:AppCommands.Exit}"/>


Wie bekomme ich es hin, dass ich noch einen Text in der Statuszeile ausgeben kann, bevor die App geschlossen wird.?
 
Mal die dumme Frage: warum überhaupt, wenn sie eh schließt? Und was heißt "Statuszeile"? Ein eigenes Control bei dir oder was Windows-spezifisches oder so? Mir fehlt noch ein bisschen der Durchblick was genau du eigentlich erreichen willst. Also...ich weiß was du meinst, aber nicht was genau du jetzt machen möchtest.

Normalerweise würde ich mich evtl. auf ein OnClosing-Event registrieren oder man triggert im Exit-Command etwas bestimmtes, wie ne Shutdown Methode in einem zentralen Service oder so...aber wie gesagt, da fehlen mir die Details. Vlt kannst du da ja mal ein genaueres Beispiel liefern.
 
Sowas lässt sich reinbasteln. Hier wird das Shutdown in einem Hintergrundtask mit einer Sekunde Verzögerung ausgelöst:

Code:
public void Execute(object parameter)
{
  // Hier würde ich jetzt gern eine Ausgabe in der Statuszeile machen, bevor die App geschlossen wird
  TextAusgeben();
  Task.Run(async ()=>
  {
     await Task.Delay(1000);
     Application.Current.Shutdown(); 
  }):
}
 
Die Statusbar ist eine normale Statusbar unten im Window. Hier kommen halt Texte rein, wie z.B. "kopiert, fertig" usw.
Die Statusbar befindet sich auf dem Hauptfenster.

@michi.o
Mit einer Methode habe ich es auch schon versucht, doch wie kann ich in der Command Klasse auf die Status Bar zugreifen?
 
Das würde in Deinem Code über den Command Parameter gehen. Wenn Deine Statusbar z.B. so definiert ist:

XML:
<Button CommandParameter="{Binding ElementName=SBarText}" />
<StatusBar VerticalAlignment="Bottom" >
   <TextBlock x:Name="SBarText"/>
</StatusBar>

Dann muss nur noch der Parameter gecasted werden:

C#:
public void Execute(object parameter)
{
  ((Textblock)parameter).Text = "123";
}

Ist jetzt aber nicht die saubere Art. Eigentlich definiert man gemäß dem MVVM Pattern ein ViewModel, welches INotifyPropertyChanged implementiert und setzt dieses ViewModel als DataContext für das Fenster. Danach kann man per Databinding Texte direkt im ViewModel ändern.

Beispiel:

Fenster:
XML:
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Content="Drück mich" Command="{Binding StatusCommand}" Width="100" Height="30" />
        <StatusBar VerticalAlignment="Bottom" >
            <TextBlock Text="{Binding StatusText}"/>
        </StatusBar>
    </Grid>
</Window>

ViewModel:
C#:
class MainWindowViewModel : INotifyPropertyChanged
{
    string _statusText;
    public string StatusText
    {
        get { return _statusText; }
        set
        {
            if (_statusText == value) return;
            _statusText = value;
            OnPropertyChanged();
        }
    }

    public ICommand StatusCommand
    {
        get
        {
            return new MyCommand(ExecuteStatusCommand, CanStatusCommandExecute);
        }
    }

    void ExecuteStatusCommand(object o)
    {
        StatusText = "123";
    }
    bool CanStatusCommandExecute(object o)
    {
        return true;
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

class MyCommand : ICommand
{
    Action<object> _execute;
    Predicate<object> _canExecute;

    public MyCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add
        {
            if (_canExecute != null)
                CommandManager.RequerySuggested += value;
        }
        remove
        {
            if (_canExecute != null)
                CommandManager.RequerySuggested -= value;
        }
    }
}
 
  • Gefällt mir
Reaktionen: Nero1
Bei dem zweiten Weg kann ich das Command gleich mit in die ModelViewKlasse schreiben? Oder ist es günstiger eine eigene Command Klasse dafür zu erstellen?

Was passiert, wenn ich noch ein zweites, anderes Command erstelle? Dann ein eigenes ViewModel?
Wäre nett, wenn du mal einen groben Plan geben könntest, wo ich wann was erstellen muss?
 
Ja, du kannst den Code, den Dein Command ausführen soll, direkt ins ViewModel schreiben. Es gibt immer ein Paar aus Execute und CanExecute. Die Klasse MyCommand in meinem Beispiel wird häufig als RelayCommand bezeichnet (meine Version ist stark vereinfacht). Mit dieser Klasse kannst Du die beiden zu einem ICommand verbinden. Ich hab mal der Vollständigkeit halber die volle Version eines Relay Commands als Textdatei angehängt.

Ein zweites ViewModel ist nicht nötig. Das Fenster kann nur einen DataContext haben. Einzelnen Elementen kann man aber wiederrum einen separaten DataContext zuweisen. Ansonsten erben die den vom Fenster.

Also kannst Du im Prinzip diesen Teil hier in deinem ViewModel beliebig oft kopieren, wenn es mehrere ICommands geben soll:

C#:
    public ICommand StatusCommand
    {
        get
        {
            return new MyCommand(ExecuteStatusCommand, CanStatusCommandExecute);
        }
    }

    void ExecuteStatusCommand(object o)
    {
        StatusText = "123";
    }
    bool CanStatusCommandExecute(object o)
    {
        return true;
    }
 

Anhänge

  • RelayCommand.txt
    2,4 KB · Aufrufe: 268
Zuletzt bearbeitet:
Langsam aber sicher verzweifele ich immer mehr.

Immer mal wieder lesen/sehe ich folgendes:

XML:
<CommandBinding Command="Help"
                    CanExecute="Help_CanExecute"
                    Executed="Help_Executed" />
  <Button.CommandBindings>


Warum wird das manchmal in XAML eingebunden?
 
Hmm... das habe ich bisher noch nicht genutzt. Von den Beispielen im Internet sieht es so aus als könnte man damit vordefinierte Standardkommandos mit einem Button verknüpfen.

Help gehört zu den vordefinierten. Ich vermute, dass hinter diesen Kommandos auch bestimmte Tastenkombinationen stecken. Help_CanExecute und Help_executed wären dann wieder Methoden im ViewModel.

cmds.png


Genaueres weiß ich leider auch nicht.
 
Ich habe das ganze jetzt mal ein wenig versucht aufzuschreiben, wie genau ich grundlegend vorgehen würde
1. MainViewModel erstellt
C#:
public   class MainWindowViewModel
    {
    }

2. Klasse anlegen wo alle meine Commands rein kommen

C#:
public static class AppCommands
    {
        public static ICommand Exit { get; set; } = new ExitCommand();
        public static ICommand Copy { get; set; } = new CopyCommand();
    }


3. Klasse anlegen welche meine Commands kennt.
C#:
internal class CopyCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            throw new NotImplementedException();
        }
    }

Am Ende sieht meine Klasse so aus:
C#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;

namespace WinInfos
{
   public   class MainWindowViewModel 
    {
        public static class AppCommands
        {
            public static ICommand Exit { get; set; } = new ExitCommand();
            public static ICommand Copy { get; set; } = new CopyCommand();
        }


        internal class CopyCommand : ICommand
        {
            public event EventHandler CanExecuteChanged;
            public bool CanExecute(object parameter)
            {
                return true;
            }

            public void Execute(object parameter)
            {
                MessageBox.Show("Copy");

            }
        }


        internal class ExitCommand : ICommand
        {
            public event EventHandler CanExecuteChanged;
            public bool CanExecute(object parameter)
            {
                return true;
            }

            public void Execute(object parameter)
            {
                MessageBox.Show("Exit");
            }
        }
    }
}


Liege ich soweit erst einmal richtig? Habe ich gravierende Fehler im Code oder in meiner Denkweise?
 
  • Gefällt mir
Reaktionen: Nero1
Klassen kommen bitte auch in separate Files. Dein Viewmodel kann die Commands dann kennen, besitzt sie aber niemals. Wie man Commands implementiert und nutzt, da gibt es verschiedene Ansätze, sollte aber so klappen wie du es machst, die Grundstruktur sieht schon nicht schlecht aus.
Wenn du bestimmte Commands immer wieder nutzt und diese nur datenunabhängige Aktionen ausführen kannst du die natürlich in einer separaten Klasse implementieren, sobald sie kontextabhängig werden müssten sie dann aber in das dazugehörige ViewModel rein.


Hier ein Beispiel aus eigener Verwendung (ggf. etwas überfordernd, bei Fragen bitte gezielt nachfragen.).

Wir benutzen in unserer Solution folgende Command-Struktur:
C#:
public class MyCommand : ICommand
    {
        private readonly Action _targetExecuteMethod;
        private readonly Func<bool> _targetCanExecuteMethod;

        public MyCommand( Action targetExecuteMethod, Func<bool> targetCanExecuteMethod )
        {
            _targetExecuteMethod = targetExecuteMethod;
            _targetCanExecuteMethod = targetCanExecuteMethod;
        }

        public MyCommand(Action targetExecuteMethod) : this(targetExecuteMethod, () => true) { }

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke( this, EventArgs.Empty );
        }

        public bool CanExecute( object parameter = null )
        {
            // wenn canExecute != null gib das zurück, sonst schau ob wenigstens Execute != null ist
            return _targetCanExecuteMethod?.Invoke( ) ?? _targetExecuteMethod != null;
        }

        public void Execute( object parameter = null )
        {
            _targetExecuteMethod?.Invoke( );
        }

        // weak reference, if command instance lifetime is longer than lifetime of UI objects hooked up to the command
        public event EventHandler CanExecuteChanged = delegate { };
    }
C#:
public class MyCommandWithParameter : ICommand
    {
        private readonly Action<object> _targetExecuteMethod;
        private readonly Func<object,bool> _targetCanExecuteMethodWithParameter;
        private readonly Func<bool> _targetCanExecuteMethod;

        public MyCommandWithParameter(Action<object> targetExecuteMethod, Func<object,bool> targetCanExecuteMethod)
        {
            _targetExecuteMethod = targetExecuteMethod;
            _targetCanExecuteMethodWithParameter = targetCanExecuteMethod;
        }

        public MyCommandWithParameter(Action<object> targetExecuteMethod, Func<bool> targetCanExecuteMethod)
        {
            _targetExecuteMethod = targetExecuteMethod;
            _targetCanExecuteMethod = targetCanExecuteMethod;
        }


        public MyCommandWithParameter(Action<object> targetExecuteMethod) : this(targetExecuteMethod, () => true) { }

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }

        public bool CanExecute(object parameter = null)
        {
            // wenn canExecute != null gib das zurück, sonst schau ob wenigstens Execute != null ist
            if ( _targetCanExecuteMethodWithParameter != null )
            {
                return _targetCanExecuteMethodWithParameter?.Invoke( parameter ) ?? _targetExecuteMethod != null;
            }
            return _targetCanExecuteMethod?.Invoke() ?? _targetExecuteMethod != null;
        }

        public void Execute(object parameter)
        {
            _targetExecuteMethod?.Invoke(parameter);
        }

        // weak reference, if command instance lifetime is longer than lifetime of UI objects hooked up to the command
        public event EventHandler CanExecuteChanged = delegate { };
    }

Ob man gewisse Feinheiten daraus benutzt (wie die alternativen Konstruktoren o. ä.) oder nicht sei mal dahingestellt, die sind nicht zwingend notwendig. Das Grundprinzip ist dabei aber relativ einfach:
  • Es gibt eine Basisklasse, die die gewünschte ICommand-Struktur implementiert. Jeweils als parameterloses Command (also nur CanExecute und Execute) sowie mit Berücksichtigung eines Parameters, der mit CommandParameter im WPF reingereicht werden kann. Das sieht dann zum Beispiel so aus:
    C#:
    ViewModel:
    // Property-Definition
    public MyCommand ExitCommand { get; set; }
    ...
    // Initialisierung im Konstruktor oder durch eine entsprechende Init-Methode
    ExitCommand = new MyCommand(Exit, CanExit);
    // alternativ: kann immer ausgeführt werden -> nutze anderen Konstruktor
    ExitCommand = new MyCommand(Exit);
    // alternativ: keine Delegaten reinwerfen, sondern Lambda-Ausdrücke benutzen
    ExitCommand = new MyCommand(() => window.Close(), () => window.IsVisible == true);
  • Im XAML wird das Command dann einfach an das UI-Element gebunden, was das können soll:
    XML:
    <!--DataContext muss im XAML oder Codebehind auf das ViewModel gesetzt sein-->
    <Button Command = {Binding ExitCommand} />
    Durch das Binding wird der Button, wenn CanExecute negativ ist, automatisch deaktiviert. Man muss dann lediglich darauf achten bei Änderungen, die die CanExecute-Bedingung verändern, ExitCommand.RaiseCanExecuteChanged(); aufzurufen.
Das Command mit Parameter funktioniert ähnlich, nur dass die Execute-Delegaten einen Parameter des Typs object bekommen, auf den man sich dann im Command beziehen kann.
Ein kleines Beispiel hierzu:
XML:
XAML:
<ListBox ItemsSource = {Binding Items}>
    <ListBox.ItemTemplate>
        <!--Man denke sich hier den ganzen nötigen Coden hin-->
            <Button Command = {Binding DataContext.EditCommand, RelativeSource = {RelativeSource Mode=FindAncestor, AncestorType=Window}}
                    CommandParameter = {Binding}/>
    </ListBox.ItemTemplate>
</ListBox>
Per Binding im CommandParameter wird das Item selbst übergeben, es entspricht {Binding Path=/}. Damit kann man dann zum Beispiel im ViewModel wieder folgendes anstellen:
C#:
ViewModel:
EditCommand = new MyCommandWithParameter(Edit, () => true);
...
private void Edit(object parameter){
    if(parameter is MyItemType item){
        item.Property1 = ...;
        item.Property2 = ...;
    }
}
Dann müssen die Properties von MyItemType lediglich INotifyPropertyChanged benutzen und die ganze UI wird automatisch aktualisiert.

------------------

Schlussendlich hast du dann eine einzelne, grundlegende Basisklasse für deine Commands. Diese werden separat im ViewModel deklariert und erzeugt. Deine UI benutzt das ViewModel als DataContext und kann darauf binden. Die Anzeigelogik verbleibt automatisiert in der UI, deine Datenlogik im ViewModel.
 
Zuletzt bearbeitet:
Nein, da hast Du was nicht verstanden. Bitte keine internen Klassen definieren und auch keine statischen (static) Klassen nutzen.
Speichere die RelayCommand Klasse, die ich oben angehängt habe in eine neue *.cs Datei, mit Namespace drum herum.

Hier nochmal mein Beispiel, angepasst mit 2 Buttons und 2 ICommands + der RelayCommand Klasse:

Code:
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="200" Width="200">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <StackPanel>
        <Button Content="Drück mich" Command="{Binding Command1}"  />
        <Button Content="Drück mich" Command="{Binding Command2}"  />
        <TextBlock Text="{Binding Text1}"/>
        <TextBlock Text="{Binding Text2}"/>
    </StackPanel>
</Window>

C#:
   class MainWindowViewModel : INotifyPropertyChanged
    {
        string _text1;
        public string Text1
        {
            get { return _text1; }
            set
            {
                if (_text1 == value) return;
                _text1 = value;
                OnPropertyChanged();
            }
        }

        string _text2;
        public string Text2
        {
            get { return _text2; }
            set
            {
                if (_text2 == value) return;
                _text2 = value;
                OnPropertyChanged();
            }
        }

        public ICommand Command1
        {
            get
            {
                return new RelayCommand(ExecuteCommand1, CanCommand1Execute);
            }
        }

        void ExecuteCommand1(object o)
        {
            Text1 = "123";
        }

        bool CanCommand1Execute(object o)
        {
            return true;
        }

        public ICommand Command2
        {
            get
            {
                return new RelayCommand(ExecuteCommand2, CanCommand1Execute2);
            }
        }

        void ExecuteCommand2(object o)
        {
            Text2 = "456";
        }

        bool CanCommand1Execute2(object o)
        {
            return true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
 
  • Gefällt mir
Reaktionen: Nero1
Zurück
Oben