Guten abend,
ich versuche einer simplen Avalonia ListBox ein Item mit Bild/Icon + Text hinzuzufügen und später den Text wieder rauszufiltern. Leider nimmt ListBox.Items.Add ein object an und nichts präziseres. Hier mal mein Versuch:
listBox.Items.Add(new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.png"))) + " PngDesc");
listBox.Items.Add(new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.ico"))) + " IcoDesc");
listBox.Items.Add(new WindowIcon(new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.png")))));
listBox.Items.Add(new WindowIcon(new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.ico")))));
listBox.Items.Add(new ListBoxItem());
listBox.Items.Add("foo");
listBox.Items.Add("bar");
Das Ergebnis sind 7 anklickbare Items und alle nur mit Text, keine Bilder/Icons aus den Avalonia Ressourcen:
Item 1: Avalonia.Media.Imaging.Bitmap PngDesc
Item 2: Avalonia.Media.Imaging.Bitmap IcoDesc
Item 3: Avalonia.Controls.WindowIcon
Item 4: Avalonia.Controls.WindowIcon
Item 5:
Item 6: foo
Item 7: bar
Beste Grüße
Avalonia ist für MVVM gebaut:
https://mycsharp.de/forum/threads/118261/artikel-mvvm-und-databinding
https://docs.avaloniaui.net/guides/basics/mvvm
Der Datentyp ist object, damit Du dein ViewModel mitgeben kannst.
Zusätzlich definierst Du an der ListBox ein ItemTemplate, was definiert, wie die Daten aus deinem ViewModel angezeigt werden sollen.
https://docs.avaloniaui.net/docs/controls/listbox
Wenn Du ein Bild anzeigen willst, brauchst Du ein Image, dem Du dann eine Source zuweist. Das "Bitmap" wäre dann deine Source.
Das Standartverhalten ruft einfach ToString auf, daher das Verhalten, was Du beobachtest.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Danke für die Antwort. Ich würde MVVM gerne etwas später benutzen. Erstmal würde ich gern wissen wie das mit ListBox.Items.Add geht um es erstmal so einfach wie möglich zu halten. Also folgende Zeile zeigt mir schonmal das Bild in der ListBox an:
listBox.Items.Add(new Image() { Source = new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.png"))) });
Wenn ich jetzt aber einen Text hinzufügen möchte, ist das Bild wieder weg:
listBox.Items.Add(new Image() { Source = new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.png"))) } + " PngDesc");
um es erstmal so einfach wie möglich zu halten
Sicher, das geht dann mit MVVM.
Nochmal:
Avalonia wurde für MVVM gebaut.
Wenn Du dich weigerst, MVVM zu benutzen, machst Du es dir nur unnötig schwer.
Man kann es zwar auch ohne MVVM nutzen, aber dann musst Du eben alles das, was mit einem ItemTemplate super einfach geht, selber bauen.
In deinem Fall heißt das, dass Du anstatt des Bildes einen horizontal ausgerichteten StackPanel hinzufügen musst, was dann das Bild und den Text als TextBlock enthält.
NuGet Packages im Code auslesen
lock Alternative für async/await
Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.
Warum MVVM erst später? Das ist so, wie: Ich möchte Autofahren, aber die Fahrerlaubnis mache ich später.
Nun zu deinem Problem. Ein Item kann nur ein Element aufnehmen. Möchtest du mehrere Elemente einem Item hinzufügen, benötigst du einen Container, wie Grid oder Stackpanel. Dieser sorgt dann dafür, dass die Elemente entsprechend angeordnet werden. Das kann man auch im Code machen, ist halt aufwendiger. Schau dir am Besten die Grundlagen von WPF an.
Ich würde MVVM gerne etwas später benutzen.
Es gibt Technologien, die darauf ausgelegt, dass gewisse Pattern verwendet werden.
Avalonia, WPF und noch weitere sind auf MVVM ausgelegt. Du wirst ohne Stolpern und Workarounds bei solchen Technologien nichts anderes verwenden können - weil es Teil des Konzepts ist.
Der konzeptionell etwas einfacher zu verstehende MVU-Pattern, der deswegen auch beliebter ist, ist offiziell noch nicht unterstützt, aber über ein Community-Projekt nachrüstbar.
https://github.com/fsprojects/Avalonia.FuncUI
Reines Code-Behind, was Du hier machst, mag Avalonia aber nicht.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Okay, ich habe mich da mal eingelesen und versucht umzusetzen. Habe jetzt das Projekttemplate "Avalonia .NET Core MVVM App (AvaloniaUI)" mit CommunityToolkit.Mvvm anstatt "Avalonia .NET Core App (AvaloniaUI)" benutzt.
In MainWindowViewModel.cs habe ich folgendes hinzugefügt:
[ObservableProperty]
private ItemCollection listBoxItems;
Da ListBox.Items vom Typ ItemCollection ist, habe ich das gewählt.
Und das einzigste Control in MainWindow.axaml sieht so aus:
<ListBox Items="{Binding ListBoxItems}" />
Leider bekomme ich nur folgenden Fehler, den ich nicht verstehe:
Unable to find suitable setter or adder for property Items of type Avalonia.Controls:Avalonia.Controls.ItemsControl for argument Avalonia.Markup:Avalonia.Data.Binding, available setter parameter lists are:
System.Object
Mache ich folgendes, funktioniert das aber auch nicht:
[ObservableProperty]
private Object listBoxItems;
Woher hast Du denn das Beispiel, dass Du ObservableProperty
auf ein ItemCollection
setzt?ItemCollection
dürfte doch ein UI Control sein, oder?
In MVVM kennt das ViewModel keine UI Controls. Das funktioniert also so konzeptionell nicht im Ansatz.
Üblicherweise hast Du in Deinem "MailViewModel" eine Bindung weitere Sub-ViewModels. zB
[ObservableProperty]
private ObservableCollection<MyItemViewModel>;
Wobei MyItemViewModel
dann die Abstrahierung mit in Deinem Fall wohl Name und Bild ist.
Les Dir durch was die Idee von MVVM ist und wie es funktioniert.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Habs so weit ans laufen bekommen. Mein Fehler oben war das ich Items benutzt habe anstatt ItemsSource. Hier mein Code:
MainWindow.axaml
<ListBox Name="listBox" ItemsSource="{Binding ListBoxItems}" Height="500" Width="500">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Bitmap}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="button1" Click="Button1_OnClick">Button 1</Button>
<Button Name="button2" Click="Button2_OnClick">Button 2</Button>
MainWindow.axaml.cs
private void Button1_OnClick(object? sender, RoutedEventArgs e)
{
((MainWindowViewModel)DataContext).ListBoxItems.Add(new ObservableBitmapAndText()
{
Bitmap = new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test.png"))),
Text = "FooBar"
});
}
private void Button2_OnClick(object? sender, RoutedEventArgs e)
{
((MainWindowViewModel)DataContext).ListBoxItems[0].Bitmap = new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/Test2.png")));
((MainWindowViewModel)DataContext).ListBoxItems[0].Text = "BarFoos";
}
ObservableBitmapAndText.cs
public class ObservableBitmapAndText : ObservableObject
{
private Bitmap _bitmap;
public Bitmap Bitmap
{
get
{
return _bitmap;
}
set
{
_bitmap = value;
OnPropertyChanged();
}
}
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
OnPropertyChanged();
}
}
}
MainWindowViewModel.cs
public partial class MainWindowViewModel : ViewModelBase
{
public AvaloniaList<ObservableBitmapAndText> ListBoxItems { get; set; } = new AvaloniaList<ObservableBitmapAndText>();
}
Muss ich jetzt wirklich jedesmal ein Field und eine Property mit OnPropertyChanged erstellen? Geht das nicht einfacher? Z.b. das ich einfach nur die Property erstelle und ein Attribut drüber setze?
Du kannst das Community Toolkit MVVM verwenden.
[ObservableProperty]
private Bitmap _bitmap;
Ach man setzt das Attribut auf die Fields. Verstehe. Funktioniert super. Danke.
Letzte Frage: Nennt man die ObservableBitmapAndText.cs zufällig BitmapAndTextModel.cs und steckt sie in den Ordner Models? Oder einen anderen Ordner? Gibt ja "Models", "ViewModels" und "Views".
Bei deinem Code geht es sehr durcheinander zu. Aus allen möglichen Google-Ecken hast du wohl Code zusammengesucht.
Mal verwendest du das CommunityToolkit, mal ReactiveUI. Warum sollte in der Listbox Items falsch sein? Siehe hier: https://docs.avaloniaui.net/docs/controls/listbox
Die Commands der Button verwendest du nicht entsprechend dem MVVM-Pattern. Siehe hier: https://docs.avaloniaui.net/docs/data-binding/binding-to-commands
Mein Code sieht dann so aus: (Ich hab die Bilder mal weggelassen.)
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
AddCommand = ReactiveCommand.Create(Add);
}
public AvaloniaList<ObservableBitmapAndText> ListBoxItems { get; set; } = new AvaloniaList<ObservableBitmapAndText>();
public ReactiveCommand<Unit, Unit> AddCommand { get; }
void Add()
{
ListBoxItems.Add(new ObservableBitmapAndText { Text = "blub" });
}
}
public class ObservableBitmapAndText : ViewModelBase
{
private string _text;
public string Text
{
get => _text;
set => this.RaiseAndSetIfChanged(ref _text, value);
}
}
<DockPanel LastChildFill="True">
<Button Content="Add" Command="{Binding AddCommand}" DockPanel.Dock="Bottom"/>
<ListBox DockPanel.Dock="Top" Name="listBox" Items="{Binding ListBoxItems}" Height="500" Width="500">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bild "/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>