none
C# | WPF : ListBox "Step by Step" befüllen als Logging RRS feed

  • Frage

  • Hallo Zusammen,

    ich bin aktuell dabei eine kleine GUI zu schreiben, welche als Log-Ausgabe eine ListBox erhalten soll.

    Mein Ziel ist es hierbei, dass diese GUI mehrer aktionen ausführt und dann nach jeder Funktion bzw. bei jedem Arbeits-Step einen neuen Eintrag in die ListBox hinzufügt, so dass man immer auf den aktuellen Stand ist, was das Tool eigentlich macht.

    Mein Problem ist es hierbei aber, dass er die Listbox nicht step bei Step befüllt sondern wartet, bis alles abegschlossen ist und dann die ListBox auf Einmal befüllt.

    Im Internet habe ich schon viele Einträge gefunden, wo dann steht man sollte nach jedem Befüllen die ListBox items Refreshen, allerdings ergibt das ganze bei mir keinen Erfolg.

    Hoffe ihr könnt mir helfen:S

    Schon einmal vielen DAnk!

    Gruß

    Tux

    Montag, 9. März 2015 06:22

Antworten

  • Hallo,
    meine Vorredner haben schon die wichtigen Stichpunkte für den theoretischen Teil genannt. Da du noch Probleme hast, möchte ich dir ein Beispiel geben, wie es funktioniert.

    Mein nachfolgendes Beispiel zeigt auch nur Ansätze wie man es machen kann - der beste Programmierstil ist es nicht.

    Zunächst empfehle ich dir eine Klasse anzulegen um ein Log-Item darstellen zu können:

    public class LogItem : INotifyPropertyChanged
    {
        private string _Notification;
    
        public string Notification
        {
            get { return _Notification; }
            set
            {
                if (_Notification != value)
                {
                    _Notification = value;
                    OnPropertyChanged();
                }
            }
        }
    
        private LogItemImportance _Importance;
    
        public LogItemImportance Importance
        {
            get { return _Importance; }
            set
            {
                if (_Importance != value)
                {
                    _Importance = value;
                    OnPropertyChanged();
                }
            }
        }
    
        #region INotifyPropertyChanged Member
    
        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var evt = this.PropertyChanged;
            if (evt != null)
                evt(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    
    }
    
    public enum LogItemImportance
    {
        Height,
        Middle,
        Low,
    }

    Im DataContext, nachfolgend einfach das Window, musst du nun eine Liste davon anlegen:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.LogItems = new ObservableCollection<LogItem>();
            this.DataContext = this;
    
            InitializeComponent();
        }
    
        public ObservableCollection<LogItem> LogItems { get; set; }

    Die Klasse implementiert INotifyPropertyChanged und das Window nutzt eine ObservableCollection<T> um die GUI über Änderungen einer Eigenschaft bzw. der Liste zu informieren.

    Im XAML kannst du dann an die Liste binden:

    <ListBox ItemsSource="{Binding LogItems}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Notification}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    In diesem Fall wird einfach von jedem Eintrag die Notification-Eigenschaft ausgegeben. Über einen ValueConverter könntest du auch noch die Wichtigkeit in Form einer Farbe darstellen etc.

    Wenn du nun kontinuierlich Einträge anzeigen willst, brauchst du einen 2. Thread. Am einfachsten geht das mittlerweile mit async und await:

    //Async kennzeichnet den Eventhandler (Button-Click o.ä.) als Asynchron
    private async void Window_Loaded(object sender, RoutedEventArgs e)
    {
        await Task.Run(async () =>
        {
            //Neuen Thread starten
            //Invoke ist bei Zugriffen auf die GUI bzw. in der GUI gebundene Listen notwendig
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "A", Importance = LogItemImportance.Low }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "B", Importance = LogItemImportance.Middle }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "C", Importance = LogItemImportance.Height }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "D", Importance = LogItemImportance.Middle }));
        });
    }

    Das ist nur eines von vielen Beispiel, wie man es machen kann. Ich kann nicht alles an dem Code erklären - dafür ist er schon zu komplex und ich kenne deinen wirklichen Wissensstand nicht. Du kannst aber gerne Rückfragen stellen.

    Wenn du noch Probleme bei der Umsetzung hast, wäre es nicht schlecht, wenn du mal deinen Code zeigst.



    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Montag, 9. März 2015 14:09
    Moderator

Alle Antworten

  • Hi Tux,
    wenn alle Arbeiten in einem thread ausgeführt werden und dabei die CPU zu 100% ausgelastet wird, dann ist es wirklich so, dass asynchrone Prozesse wie das Aktualisieren der Oberfläche u.U. sehr spät ausgeführt werden können. Wenn jedoch der thread regelmäßig wartet, dann kann man über INotifyPropertyChanged die Oberfläche informieren, dass sie sich die anzuzeigenden Daten selbst holt. Wenn der Prozess jedoch zu 100% die CPU auslastet, dann sollte man über multi-threading nachdenken und den Vordergrund-thread immer auf Ereignisse warten lassen. In diesem Fall kann die Oberfläche zeitnah aktualisiert werden.

    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks

    Montag, 9. März 2015 07:01
  • Schon einmal vielen Dank für die Info!

    Ich werde mir dann einmal "INotifyPropertyChanged" durchlesen. Vielleicht bekomme ich es ja damit hin.

    Montag, 9. März 2015 07:06
  • Ich glaube das Wichtigste hierbei wäre, dass Du dann eine ObservableCollection mit Binding nutzt und nicht die Box mit einzelnen Items füllst. Sieht stark kompliziert aus wenn man es noch nie gemacht hat, aber es lohnt sich damit zu arbeiten.
    Montag, 9. März 2015 08:11
  • Kenne mich hier leider noch nicht so gut aus. habe nun einen Test mit ObservableCollection gemacht, leider habe ich hier das gleiche Problem das mein komplettes "Log" auf einmal angezeigt wird und nicht Step by Step.

    Wenn ich nach jedem Logeintrag allerdings eine MessageBox oder so anzeige dann funktioniert das zeilenweise Loggen :/

    
    Montag, 9. März 2015 08:44
  • Hallo,
    meine Vorredner haben schon die wichtigen Stichpunkte für den theoretischen Teil genannt. Da du noch Probleme hast, möchte ich dir ein Beispiel geben, wie es funktioniert.

    Mein nachfolgendes Beispiel zeigt auch nur Ansätze wie man es machen kann - der beste Programmierstil ist es nicht.

    Zunächst empfehle ich dir eine Klasse anzulegen um ein Log-Item darstellen zu können:

    public class LogItem : INotifyPropertyChanged
    {
        private string _Notification;
    
        public string Notification
        {
            get { return _Notification; }
            set
            {
                if (_Notification != value)
                {
                    _Notification = value;
                    OnPropertyChanged();
                }
            }
        }
    
        private LogItemImportance _Importance;
    
        public LogItemImportance Importance
        {
            get { return _Importance; }
            set
            {
                if (_Importance != value)
                {
                    _Importance = value;
                    OnPropertyChanged();
                }
            }
        }
    
        #region INotifyPropertyChanged Member
    
        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var evt = this.PropertyChanged;
            if (evt != null)
                evt(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    
    }
    
    public enum LogItemImportance
    {
        Height,
        Middle,
        Low,
    }

    Im DataContext, nachfolgend einfach das Window, musst du nun eine Liste davon anlegen:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.LogItems = new ObservableCollection<LogItem>();
            this.DataContext = this;
    
            InitializeComponent();
        }
    
        public ObservableCollection<LogItem> LogItems { get; set; }

    Die Klasse implementiert INotifyPropertyChanged und das Window nutzt eine ObservableCollection<T> um die GUI über Änderungen einer Eigenschaft bzw. der Liste zu informieren.

    Im XAML kannst du dann an die Liste binden:

    <ListBox ItemsSource="{Binding LogItems}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Notification}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    In diesem Fall wird einfach von jedem Eintrag die Notification-Eigenschaft ausgegeben. Über einen ValueConverter könntest du auch noch die Wichtigkeit in Form einer Farbe darstellen etc.

    Wenn du nun kontinuierlich Einträge anzeigen willst, brauchst du einen 2. Thread. Am einfachsten geht das mittlerweile mit async und await:

    //Async kennzeichnet den Eventhandler (Button-Click o.ä.) als Asynchron
    private async void Window_Loaded(object sender, RoutedEventArgs e)
    {
        await Task.Run(async () =>
        {
            //Neuen Thread starten
            //Invoke ist bei Zugriffen auf die GUI bzw. in der GUI gebundene Listen notwendig
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "A", Importance = LogItemImportance.Low }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "B", Importance = LogItemImportance.Middle }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "C", Importance = LogItemImportance.Height }));
            await Task.Delay(1000);//1s warten
            Dispatcher.Invoke(() => LogItems.Add(new LogItem() { Notification = "D", Importance = LogItemImportance.Middle }));
        });
    }

    Das ist nur eines von vielen Beispiel, wie man es machen kann. Ich kann nicht alles an dem Code erklären - dafür ist er schon zu komplex und ich kenne deinen wirklichen Wissensstand nicht. Du kannst aber gerne Rückfragen stellen.

    Wenn du noch Probleme bei der Umsetzung hast, wäre es nicht schlecht, wenn du mal deinen Code zeigst.



    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Montag, 9. März 2015 14:09
    Moderator