Bei Button1_Click ein Label und einen Button erstellen C#?

1 Antwort

Vom Fragesteller als hilfreich ausgezeichnet

Nutze eine ListView, an die du ein Model bindest.

Das Model:

public class ToDo
{
  public string Comment { get; set; }
}

Das bedarf wohl kaum einer weiteren Erklärung.

Das ViewModel:

public class ToDoViewModel
{
  public ToDoViewModel()
  {
    ToDos = new OberservableCollection<ToDo>();
  }

  public ObservableCollection<ToDo> ToDos { get; set; }

  public ICommand AddToDo => new RelayCommand<string>(comment => ToDos.Add(new ToDo { Comment = comment }));

  public ICommand RemoveToDo => new RelayCommand<string>(comment =>
  {
    var toDoToRemove = ToDos.FirstOrDefault(toDo => toDo.Comment == comment);

    if (toDoToRemove != null)
    {
      ToDos.Remove(toDoToRemove);
    }
  });
}

Dieses stellt dem View die Liste zur Verfügung sowie Commands, um Einträge hinzuzufügen oder wieder zu löschen. Bei der Liste handelt es sich konkret um eine ObservableCollection. Diese hat die Eigenheit, bei Datenbindung ihre Beobachter automatisch darüber informieren zu können, falls ein Eintrag hinzukommt oder entfernt wird.

Bezüglich der RelayCommand-Implementation: Hierbei handelt es sich um eine Standardimplementation, wie du sie auch in WPF-Toolkits / -Ergänzungsframeworks finden kannst. Die Besonderheit eines Command besteht im Prinzip daraus, dass seine Funktionalität an eine Bedingung (Predicate) geknüpft werden kann. Wird das Predicate nicht erfüllt, ist der Command nicht ausführbar. An so einen Command gebundene Buttons werden deaktiviert. Du könntest dies für deinen Add-Command ausnutzen, um bspw. den Button erst zu aktivieren, sobald eine Eingabe im Textfeld steht. Dies aber nur am Rande. Damit kannst du dich beizeiten selbst beschäftigen.

public class RelayCommand<T> : ICommand
{
  private readonly Predicate<T> _canExecute;

  private readonly Action<T> _execute;

  public RelayCommand(Action<T> execute) : this(execute, null)
  {
  }

  public RelayCommand(Action<T> execute, Predicate<T> canExecute)
  {
    if (execute == null)
    {
      throw new ArgumentNullException("execute");
    }

    _canExecute = canExecute;
    _execute = execute;
  }

  public bool CanExecute(object parameter)
  {
    return _canExecute == null ? true : _canExecute((T)parameter);
  }

  public event EventHandler CanExecuteChanged
  {
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
  }

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

Das View (Attribute, die hier nicht von großer Interesse sind, lasse ich raus):

<Window xmlns:vm="clr-namespace:NamespaceOfYourToDoViewModel">
  <Window.DataContext>
    <vm:ToDoViewModel />
  </Window.DataContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="0.2*" />
      <RowDefinition Height="0.8*" />
    </Grid.RowDefinitions>
    <StackPanel>
      <TextBox Name="CommentTextBox" />
      <Button Command="{Binding AddToDo}" CommandParameter="{Binding Path=Text, ElementName=CommentTextBox}" Content="Add" />
    </StackPanel>
    <ListView Grid.Row="1" ItemsSource="{Binding ToDos}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <StackPanel>
            <Label Content="{Binding Comment}" />
            <Button Command="{Binding Path=DataContext.RemoveToDo, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CommandParameter="{Binding Comment}" Content="Remove" />
          </StackPanel>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
</Window>

Am Anfang wird erst einmal der Datenkontext für das View definiert. Dazu wird (implizit von WPF) eine Instanz deiner ViewModel-Klasse angelegt.

Für das Hinzufügen neuer Einträge wird ein Button an den AddToDo-Command gebunden. Als Argument (CommandParameter) liefert er den Text des Textfelds mit, welches vor ihm steht. Um diesen Text zu erhalten, nutze ich erneut ein Binding, diesmal an das Text-Property der Textbox.

Interessant wird es dann nochmals bei der ListView. Das ItemsSource-Attribut legt die Datenquelle fest. An dieser Stelle wird die ObservableCollection an die Komponente gebunden. Das heißt, die Komponente generiert automatisch die ListItems, die notwendig sind, um die Liste zu repräsentieren. Die gibt ihr Bescheid, falls ein Eintrag ergänzt / entfernt wurde.

Mit dem ItemTemplate-Attribut lässt sich folgend der Grundaufbau eines ListItem definieren. Es soll ein Label und einen Button beinhalten. Da der Datenkontext der ListView dem seiner ItemsSource entspricht und somit jeder Eintrag der ObservableCollection auf ein ListItem gemappt wird, kann jedes ListItem via Binding auf die Properties des ToDo-Typs zugreifen. In diesem Fall also Comment.

Bei dem Button wiederum besteht das Problem, dass er für seinen Command den Datenkontext des ViewModels, nicht der ObservableCollection benötigt. Daher referenziere ich für das Binding auf die Window-Komponente. Ihr DataContext-Property zeigt auf das ToDoViewModel-Objekt.

Bezüglich der Anordnung von Komponenten: Nutze ein Grid. Du kannst für dieses vorgeben, wie viele Spalten / Zeilen es geben soll und wie sie gewichtet werden.

In diesem Beispiel fasst das Grid drei Spalten und zwei Zeilen.

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="0.2*">
    <ColumnDefinition Width="0.2*">
    <ColumnDefinition Width="0.6*">
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="0.2*" />
    <RowDefinition Height="0.8*" />
  </Grid.RowDefinitions>

  <!-- your components ... -->
</Grid>

Mit den Attributen Height und Width kannst du die Breite festlegen. Mit dem Sternchen als Einheit erreichst du eine prozentuale Aufteilung. Für 20% könntest du bspw. 20* schreiben oder wie bei mir 0.2*.

Deine Komponenten kannst du anschließend mit Grid.Column / Grid.Row in die richtige Zelle setzen. Lässt du die Attribute weg, ist der Standardwert 0 (also die erste Spalte / Zeile).

<Label Content="In second column, third row" Grid.Column="1" Grid.Row="2" />

Ich würde dir im Folgenden empfehlen, dich mit folgenden Themen auseinanderzusetzen:

  • MVVM
  • Bindings
  • Commands
  • DataContext

Lernquellen zu WPF / hilfreiche Artikel zu MVVM findest du hier:

Was noch verbessert werden könnte (ich aber dir überlasse): Abgesehen vom Layout/Design, ist der Prozess des Entfernens von ToDos noch nicht so schön. Zum Einen erlaubt er keine Duplikate (was aber wohl noch der Anforderung entspricht) und zum anderen sind die Vergleichsobjekte unregelmäßig. Womöglich sollte man dem Model eine ID fester Länge hinzufügen (z.B. ein Guid) und die stattdessen benutzen, um zu löschende Einträge zu finden.

AaronBF 
Fragesteller
 28.12.2021, 13:17

Sehr vielen dank, das ist alles etwas kompliziert, ich werde mich damit befassen :-)

0