Laden...

Avalonia ListBox Item mit Bild/Icon + Text hinzufügen und später den Text wieder rausfiltern

Erstellt von TheCrossPlatformGuy37 vor 9 Monaten Letzter Beitrag vor 9 Monaten 693 Views
T
TheCrossPlatformGuy37 Themenstarter:in
5 Beiträge seit 2023
vor 9 Monaten
Avalonia ListBox Item mit Bild/Icon + Text hinzufügen und später den Text wieder rausfiltern

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

2.071 Beiträge seit 2012
vor 9 Monaten

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.

T
TheCrossPlatformGuy37 Themenstarter:in
5 Beiträge seit 2023
vor 9 Monaten

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");
2.071 Beiträge seit 2012
vor 9 Monaten

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.

190 Beiträge seit 2012
vor 9 Monaten

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.

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF

16.783 Beiträge seit 2008
vor 9 Monaten

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.

T
TheCrossPlatformGuy37 Themenstarter:in
5 Beiträge seit 2023
vor 9 Monaten

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;
16.783 Beiträge seit 2008
vor 9 Monaten

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 MyItemViewModeldann die Abstrahierung mit in Deinem Fall wohl Name und Bild ist.

Les Dir durch was die Idee von MVVM ist und wie es funktioniert.

T
TheCrossPlatformGuy37 Themenstarter:in
5 Beiträge seit 2023
vor 9 Monaten

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?

187 Beiträge seit 2009
vor 9 Monaten

Du kannst das Community Toolkit MVVM verwenden.

[ObservableProperty]
private Bitmap _bitmap;
T
TheCrossPlatformGuy37 Themenstarter:in
5 Beiträge seit 2023
vor 9 Monaten

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".

190 Beiträge seit 2012
vor 9 Monaten

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>
  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF