Laden...

Terminübersicht mit WPF

Erstellt von Rene.Schustek vor 8 Jahren Letzter Beitrag vor 8 Jahren 2.462 Views
R
Rene.Schustek Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren
Terminübersicht mit WPF

Hallo,
ich stehe gerade richtig auf dem Schlauch. Ich möchte gerne eine Terminübersicht erstellen, wobei die Termine in einer Zeitleiste untereinander dargestellt werden. Kein Problem bis hier hin. Doch es können x Termine für den Zeitraum gebucht werden. Also müsste es mehrere Spalten geben, um die Termine darstellen zu können. Die Termine kommen aus einer Datenbank und beinhalten die Start- und die Endzeit.

Bisher habe ich es mit einer DataTable versucht, die ich entsprechend erweitere und die einzelnen Zellen mit dem Objekt fülle. Aber das bekomme ich nicht hin. Irgendwo habe ich einen Denkfehler.

Folgenden Ablauf habe ich mir überlegt:
Die DataTable wird mit der ersten Spalte angelegt. Diese enthält das TimeListObject:

public class TimeListObject
    {
        public int Time { get; set; }
        public string TimeName { get; set; } 
    }

Da in 15Min Schritten angezeigt wird, wird nur bei jeder 4 Zeile der TimeName angezeigt.

Jetzt durchlaufe ich alle Termine. Das Object hat folgenden Aufbau:

public class CalendarAppointment
    {
        public int AppointmentID { get; set; }
        public int StartMinutes { get; set; }
        public int EndMinutes { get; set; }
        public string Name { get; set; }
        public string StartTime { get; set; }
        public string EndTime { get; set; }
        public int CustomerID { get; set; }
        public string Color { get; set; }
    }

Für jeden Termin schaue ich ich, ob eine Spalte bereits existiert. Existiert keine Spalte, wird diese angelegt und mit dem Termin gefüllt. Ist die Zeit durch den Termin belegt, werden die Zellen in der Spalte mit dem Object gefüllt. Existiert eine Spalte und diese bereits mit dem CalendarAppointment-Objekt gefüllt ist, lege ich eine neue Spalte an und fülle diese entsprechen.

Wie würdet ihr das Problem lösen und wie stelle ich die Daten dar? Bei dem Rest des Projektes arbeite ich mit Prism.

Danke!
René

5.299 Beiträge seit 2008
vor 8 Jahren

Doch es können x Termine für den Zeitraum gebucht werden. Das ist problematisch, schwierig zu handeln und müsstest du mal näher erläutern.

Wie kommt zustande, dass zum selben Zeitpunkt mehrere Termine stattfinden, und wieso sollten die überhaupt in derselben View angezeigt werden?

Auch habich Mühe, mir das in Spalten vorzustellen - in meiner Welt hat eine Spalte eine Spalten-Überschrift, aber womit überschreibt man eine Spalte, die mal da ist, mal nicht?

Der frühe Apfel fängt den Wurm.

T
314 Beiträge seit 2013
vor 8 Jahren

Warum orientierst Du dich nicht an z.B. Outlook?

Vorstellen könnte ich mir auch ein Grid mit sehr vielen Spalten und Du kümmerst dich einfach nur um das korrekte Setzen der Column und ColumnSpan Eigenschaft?

R
Rene.Schustek Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Hallo,
danke für die Hilfe. Das ganze soll ein Gruppen-Kalendar sein. Daher können mehrere Termine zu einem Zeitpunkt dargestellt werden.

Aussehen sollte das ganze dann so, wie im Anhang ([Link auf externe Source entfernt]

Ich bin die ganze Zeit am überlegen, welche Controls ich nehmen soll und wie ich das Binding dann durchführen soll. Und gefühlt programmiere ich die letzten Wochen im Kreis. Die Tipps im Netz sind alle für WindowsForms und CodeBehind gedacht.

Bei Outlook werden die Termine dann ja auch nebeneinander gesetzt. Da hätte ich dann die Idee, dass ich eine ListView verwende, die dann als Content ein horizontale ListBox oder ein StackPanel verwendet. Da müsste ich dann für jede Zeile ein Array erzeugen und das in eine IList<T> packen. Dann müsste das Binding auch klappen. Oder?

Danke und Grüße!
René

PS: Ich bin hier der einzige Programmierer und da fehlt einem echt manchmal der Austausch. Daher dickes Danke.

Hinweis von Coffeebean vor 8 Jahren

Link auf externe Bildsource entfernt. Es hängt ja schon hier am Beitrag.

5.299 Beiträge seit 2008
vor 8 Jahren

Uih - das wird knackig!

Ich würde auf ein Grid als ItemsPanelTemplate eines ItemsControls setzen, mit vom Viewmodel gesteuerten Zeilen und Spalten.

Dann brauchst du einen sehr listigen Algo, der die Termine durchgeht, und Überlappungen feststellt, auch das n bei n-fachen Überlappungen - dieses n definiert ja die Spalten-Anzahl.

Und dann aus den Terminen Viewmodelse bilden, mit bindebaren Properties für Zeile, Spalte, RowSpan.

Sorry - ist nur grob angedacht - für konkreteres müsste ich selbst stundenlange Experimente machen.
Evtl. muss man sogar von Grid erben, und was basteln, damit das Grid seine Row-/Column-Definitions per Bindings steuern kann - weiß nicht.

Der frühe Apfel fängt den Wurm.

709 Beiträge seit 2008
vor 8 Jahren

Evtl. muss man sogar von Grid erben, und was basteln, damit das Grid seine Row-/Column-Definitions per Bindings steuern kann - weiß nicht.

Ich schon mehrere Blogs gesehen, die erklären, wie das relativ unkompliziert per Attached Behavior gelöst werden kann.

R
Rene.Schustek Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

So, hat geklappt. Hier die Lösung. Wird zwar noch zu optimieren sein, aber erst einmal kommt das raus, was ich wollte und ich muss meinem Chef was präsentieren.

Folgendermaßen bin ich jetzt vorgegangen:
Ich hole mir alle Termine für den Benutzer (nach Startzeit sortiert). Dann erzeuge ich ein DataTable und fülle die Zellen mit den entsprechenden Object. Ist die 1. Spalte für z.B. 8:00 Uhr schon belegt, dann füge ich eine neue Spalte hinzu und befülle diese. Sind alle Termine verteilt, lese ich das DataTable Zeilenweise aus und erstelle für jede Zeile mein Object für die View. Dieses enthält zum einem die Zeit und dann eine IList für alle Termine. So kann ich dann über eine ListBox die eine ListView enthält, alles anzeigen.

Jetzt muss es nur noch etwas schöner werden ... . Hier die bisherige Lösung, falls jemand suchen sollte.



public class ListObject
{
        public string TimeName { get; set; }
        public int Time { get; set; }
        public IList<CalendarAppointment> Appointment { get; set; }
}

public class CalendarAppointment : ICloneable
{
        public int AppointmentID { get; set; }
        public int StartMinutes { get; set; }
        public int EndMinutes { get; set; }
        public string Name { get; set; }
        public string StartTime { get; set; }
        public string EndTime { get; set; }
        public int CustomerID { get; set; }
        public string Color { get; set; }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
}

public class AppointmentItem
    {
        public string Name { get; set; }
        public string Color { get; set; }
    }

    public class TableObject
    {
        public int ID { get; set; }
        public int Name { get; set; }
    }

public static IList<CalendarAppointment> _tempAllAppointments;

        public static IList<ListObject> CreateCalendarList(int userID, DateTime date, string startTime, string endTime, int durationInMinutes)
        {
            IList<ListObject> rowList = new List<ListObject>();

            //Kein gültiges Datum
            if (date.ToShortDateString() == "01.01.0001")
                return rowList;

            DataTable calendarDataTable = Calendar(userID, date, startTime, endTime, durationInMinutes);            

            for (int rowCount = 0; rowCount < calendarDataTable.Rows.Count; rowCount++ )
            {
                ListObject rowObject = new ListObject();

                TimeListObject timeObject = (TimeListObject)calendarDataTable.Rows[rowCount][0];
                rowObject.Time = timeObject.Time;
                rowObject.TimeName = timeObject.TimeName;

                IList<CalendarAppointment> appointmentList = new List<CalendarAppointment>();

                for (int columnCount = 1; columnCount < calendarDataTable.Columns.Count; columnCount ++) //erste Spalte überspringen, enthält die Zeiten
                {
                    if (System.DBNull.Value.Equals(calendarDataTable.Rows[rowCount][columnCount])) //kein Eintrag vorhanden 
                    {
                        //Leeren Eintrag erstellen
                        CalendarAppointment appointment = new CalendarAppointment();
                        appointment.Name = string.Empty;
                        appointment.StartMinutes = 0;
                        appointment.EndMinutes = 0;
                        appointment.StartTime = string.Empty;
                        appointment.EndTime = string.Empty;
                        appointment.Color = "#FFFFFF";
                        appointment.AppointmentID = -1;

                        appointmentList.Add(appointment);

                    }
                    else //Eintrag vorhanden
                    {
                        CalendarAppointment calendarAppointment = new CalendarAppointment();
                        calendarAppointment = (CalendarAppointment)calendarDataTable.Rows[rowCount][columnCount];

                        //Kopie erstellen
                        CalendarAppointment newAppointment = (CalendarAppointment)calendarAppointment.Clone();

                        //Nicht die Startzeit: Name nicht anzeigen
                        if (newAppointment.StartMinutes != timeObject.Time)
                            newAppointment.Name = string.Empty;                        

                        appointmentList.Add(newAppointment);
                    }
                }

                rowObject.Appointment = appointmentList;
                rowList.Add(rowObject);
            }

            return rowList;
        }

        /// <summary>
        /// Erzeuge ein DataTable mit der Übersicht aller Termine
        /// </summary>
        /// <param name="userID">Die ID des angemeldeten Benutzers</param>
        /// <param name="date">Das ausgewählte Datum</param>
        /// <param name="startTime">Die Startzeit für die TimeLine</param>
        /// <param name="endTime">Die Endzeit für die TimeLine</param>
        /// <param name="durationInMinutes">Die Dauer der Unterteilung zwischen den vollen Stunden</param>
        /// <returns>Ein gefülltes DataTable.</returns>
        private static DataTable Calendar(int userID, DateTime date, string startTime, string endTime, int durationInMinutes)
        {
            DataTable calendarDataTable = new DataTable();
            
            //Erzeuge Liste mit allen Terminen
            IList<CalendarAppointment> allAppointments = new List<CalendarAppointment>();

            IList<int> groupList = GetGroupIDs(userID);
            foreach (int groupID in groupList)
            {
                IList<CalendarAppointment> appointmentList = GetAppointments(groupID, date);
                foreach (CalendarAppointment appointment in appointmentList)
                    allAppointments.Add(appointment);
            }

            //Teile Termine auf Spalten auf
            calendarDataTable = CreateCalendarDataTable(allAppointments, startTime, endTime, durationInMinutes);

            return calendarDataTable;
        }

        //ToDo: Algorythmus später überarbeiten: Prüfen, warum leere Spalten vorhanden sind
        /// <summary>
        /// Säubert die DataTable von leeren Spalten.
        /// </summary>
        /// <param name="calendarDataTable">Die gefüllte DataTable</param>
        /// <returns>Die gesäuberte DataTable</returns>
        private static DataTable CleanDataTable(DataTable calendarDataTable)
        {            
            for(int columnCount = 0; columnCount < calendarDataTable.Columns.Count; columnCount++ )
            {
                bool columnHasValue = false;

                for(int rowCount = 0; rowCount < calendarDataTable.Rows.Count; rowCount++)
                {
                    if (!System.DBNull.Value.Equals(calendarDataTable.Rows[rowCount][columnCount]))
                        columnHasValue = true;
                }

                //Kein Eintrag: Spalte löschen
                if (!columnHasValue)
                {
                    DataColumn column = calendarDataTable.Columns[columnCount];
                    calendarDataTable.Columns.Remove(column);
                    CleanDataTable(calendarDataTable);
                }
            }

            return calendarDataTable;
        }

        /// <summary>
        /// Füllt die DataTable mit den Terminen.
        /// </summary>
        /// <param name="allAppointments">Die Terminliste</param>
        /// <param name="calendarDataTable">Das DataTable</param>
        /// <returns>Das gefüllte DataTable</returns>
        private static DataTable CreateDataTable(IList<CalendarAppointment> allAppointments, DataTable calendarDataTable)
        {
            _tempAllAppointments = allAppointments;

            for (int rowCount = 0; rowCount < calendarDataTable.Rows.Count; rowCount++)
            {
                DataRow actualRow = calendarDataTable.Rows[rowCount];
                TimeListObject actualTimeObject = (TimeListObject)actualRow[0];
                int actualTimeMinutes = actualTimeObject.Time;

                bool appointmentProceed = false;

                foreach (CalendarAppointment appointment in _tempAllAppointments)
                {
                    //Erste Spalte anlegen
                    if (calendarDataTable.Columns.Count == 1)
                    {
                        DataColumn appointmentColumn = new DataColumn("", typeof(CalendarAppointment));
                        calendarDataTable.Columns.Add(appointmentColumn);
                    }

                    //Neuer Termin
                    if (appointment.StartMinutes == actualTimeMinutes)
                    {
                        //Spalten durchlaufen           
                        for (int columCount = 1; columCount < calendarDataTable.Columns.Count; columCount++)
                        {
                            if (System.DBNull.Value.Equals(calendarDataTable.Rows[rowCount][columCount])) //Kein Eintrag
                            {
                                calendarDataTable.Rows[rowCount][columCount] = appointment;
                                _tempAllAppointments.Remove(appointment);

                                //Gesamten Zeitbereich füllen
                                for(int appointmentRowCount = rowCount + 1; appointmentRowCount < calendarDataTable.Rows.Count; appointmentRowCount ++)
                                {
                                    TimeListObject timeActualMinutes = (TimeListObject)calendarDataTable.Rows[appointmentRowCount][0];
                                    int tempActualMinutes = timeActualMinutes.Time;

                                    if (appointment.EndMinutes > tempActualMinutes)
                                        calendarDataTable.Rows[appointmentRowCount][columCount] = appointment;
                                    else
                                        break;
                                }

                                appointmentProceed = true;
                                break;
                            }
                            else //Eintrag vorhanden = neue Spalte anlegen
                            {
                                DataColumn appointmentColumn = new DataColumn("", typeof(CalendarAppointment));
                                calendarDataTable.Columns.Add(appointmentColumn);
                                appointmentProceed = true;
                            }
                        }
                    }

                    if (appointmentProceed)
                        break;
                }

                if (appointmentProceed)
                    break;
            }

            if (_tempAllAppointments.Count > 0)
                calendarDataTable = CreateDataTable(_tempAllAppointments, calendarDataTable);

            calendarDataTable = CleanDataTable(calendarDataTable);

            return calendarDataTable;

        }

        /// <summary>
        /// Erzeugt das DataTable.
        /// </summary>
        /// <param name="allAppointments">Liste aller Termine</param>
        /// <param name="startTime">Die Startzeit für die TimeLine</param>
        /// <param name="endTime">Die Endzeit für die TimeLine</param>
        /// <param name="durationInMinutes">Die Dauer der Unterteilung zwischen den vollen Stunden</param>
        /// <returns></returns>
        private static DataTable CreateCalendarDataTable(IList<CalendarAppointment> allAppointments, string startTime, string endTime, int durationInMinutes)
        {
            DataTable calendarDataTable = new DataTable();

            IList<TimeListObject> timeList = CreateTimeLine.Create(startTime, endTime, durationInMinutes);

            DataColumn timeColumn = new DataColumn("Zeit", typeof(TimeListObject));
            calendarDataTable.Columns.Add(timeColumn);

            foreach(TimeListObject time in timeList )
            {
                DataRow drTimeRow = calendarDataTable.NewRow();
                drTimeRow[0] = time;
                calendarDataTable.Rows.Add(drTimeRow);                
            }

            calendarDataTable = CreateDataTable(allAppointments, calendarDataTable);

            return calendarDataTable;
        }

Und dann entsprechend die View:


<ListBox ItemsSource="{Binding GuiCalendarList}" Margin="0">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" Margin="0"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>

            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <ContentPresenter />
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
                
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="0">
                        
                        <Label Background="#DBF6FF" BorderBrush="LightGray" BorderThickness="1" Content="{Binding TimeName}" Width="100"/>

                        <ListView ItemsSource="{Binding Appointment}">
                            <ListView.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Orientation="Horizontal"  Margin="0"/>
                                </ItemsPanelTemplate>
                            </ListView.ItemsPanel>
                            
                            <ListBox.ItemContainerStyle>
                                <Style TargetType="{x:Type ListBoxItem}">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                <ContentPresenter Margin="5,0,5,0"/>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ListBox.ItemContainerStyle>
                            
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <Label Content="{Binding Name}" Height="25" Margin="0" FontSize="10" Background="{Binding Color}" Width="200"/>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>       
                        
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Im ViewModel ist dann GuiCalendarList einfach die IList<ListObject>:


private IList<ListObject> guiCalendarList;
        public IList<ListObject> GuiCalendarList
        {
            get
            {
                GuiCalendarList = guiCalendarList;
                return guiCalendarList;
            }
            set
            {
                if (guiCalendarList == value)
                    return;
                SetProperty(ref guiCalendarList, value);
            }
        }

Danke für die Hilfe!
René

5.299 Beiträge seit 2008
vor 8 Jahren

sieht sehr interessant aus, aber bischen ville, ums zu durchblicken.
V.a. sehe ich nix, wo nun die Daten eiglich geladen werden.

Und das generiert jetzt wirklich sone Oberfläche, wie in post#4 abgebildet?

Falls ja - magst du das vlt. zippen und in die c#-Snippets stellen?
Da können noch mehr Leuts was von haben, denkich.

Der frühe Apfel fängt den Wurm.

R
Rene.Schustek Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Dann passe ich das am WE mal an, dass man dann alles sieht und mache noch eine kleine Beschreibung.

Grüße!
René

F
10.010 Beiträge seit 2004
vor 8 Jahren

Schönen Stackoverflow hast du dir da im public GuiCalendarList Setter gebastelt.

J
641 Beiträge seit 2007
vor 8 Jahren

cSharp Projekte : https://github.com/jogibear9988

R
Rene.Schustek Themenstarter:in
5 Beiträge seit 2015
vor 8 Jahren

Danke für die Hinweise und die Links. Die ersten kannte ich bereits, da kam ich aber nicht mit klar. Den anderen werde ich mir mal in Ruhe zu Gemüte führen.

Schönen Tag noch!
René