Timing problem

Feb 26, 2010 at 4:57 PM

There is a "timing problem" when the ClearEvents() and/or ResetEvents() methods are called before the ControlLoaded() event has been fired for the timeline object. Maybe this is the cause to the various null exception reports in this forum.

I get this problem when the timeline object is instantiated in code;  I need to adjust the MinDateTime and MaxDateTime settings based on the input data. Since these properties (by some reason) cannot be changed once the timeline object is loaded, I must create the object in code, set the properties and then attach the object to the page. This works as expected, but a problem occurs when I attempt to load data using the ResetEvents() method. If I call ResetEvents() in a button click event handler (as decsribed in other posts) there is no problem because the timeline object is fully loaded when the button is clicked. But if I'd like to call ResetEvents() in code (after instantiating the timeline object) I run into problems. The TimelineTray.RefreshEvents() method will throw a nullexception because the m_mainband property is null.

Now, the m_mainband property is set in the ControlLoaded() event handler, hence I must call ResetEvents() after the ControlLoaded() event is fired, but how can I do that? I have tried to attach to the Loaded() event for the timeline object, but that seems to be fired before the ControlLoaded() event. And I cannot find any event that is always fired after the ControlLoaded(), so I'm stuck.

For me, the best solution would be to have the MinDateValue and MaxDateValue properties changeable at any time (so I don't have to instantiate the object in code) but that is probably not the final solution to these problems.

A possible solution would be to implement a "ready_for_data" event that I can attach to; when the timeline object is fully loaded and initialized, this event could be fired and I can call ResetEvents() from that event handler.

Note: despite the nullexception described above, the majority of the timeline view actually works, provided that I do NOT call ClearEvents(), and that I have an exception handler for the nullexception fired by TimelineBand when calling ResetEvents() (an exception handler that basically ignores the exception and continues as if nothing has happened). My events will be properly added before the nullexception occur, and the remaining formatting takes place when the ControlLoaded() event fires. I do get some artifacts where the box surrounding the mini events in the timeline band isn't correctly sized/positioned in comparison to the real display;  I don't know if this really is a side effect of the nullexception or if it is another bug, but looking at the code I find it plausible that the things are related.

 

Coordinator
Feb 27, 2010 at 12:03 AM

Yes, MinDateTime/MaxDateTime cannot be changed, it would be nice though. Maybe something of these would work:

- Try SizeChangedon TimelineTray

- I guess you can try to do something similar to javascript code and then call ClearEvents/ResetEvents:

            timeline.MinDateTime = "01/01/1830";
            timeline.MaxDateTime = "01/01/1930";
            timeline.AddTimelineBand(0, true, "years", 20, 130);
            timeline.AddTimelineBand(50, false, "decades", 20, 4);
            timeline.Run();

Let me know.

 

Feb 27, 2010 at 11:49 AM

Even if I do things in Javascript, I still need some point where I can call ResetEvents(). I need an event to be fired after OnControlLoaded() has been run.

I made some tests where I ignored my previous requirement to change the MinDateTime and MaxdateTime, thereby making it possible to have the timline object fully created and instantiated in XAML. Then I hit a new obstacle, and it feels like I'm closing in on a "this-can't-be-done" situation.

When I create the timline object in XAML as opposed to instantiate it in code, I get loads of Assert messages saying that things are wrong. And the thing that is "wrong" is that the timeline object isn't actually visible when the page is created. The page has TabControl and the timeline is located on a TabItem that is not focused when the page is created. Hence the "ActualWidth" and "ActualHeight" is 0, and everything goes wrong.

So...I'm back to the case where the only possible solution is to dynamically create the timeline object in code when the TabItem receive focus. And then I'm back to the situation that the call to ResetEvents() fires a nullexception because OnControlLoaded() hasn't been run yet.

Some pseudo code for what I'd like to do:

List<TimelineEvent> events;

void TabItem_getFocus()
{
    TimelineUserControl TL = new TimelineUserControl();
    CultureInfo ci = CultureInfo.CurrentCulture;
    TL.timeline.CultureID = ci.Name;

    DateTime start = DateTime.Now;
    DateTime end = DateTime.Now;

    events = <get list of events from WCF service>

    foreach (TimelineEvent ev in events)
    {
        if (ev.StartDate < start) start = ev.StartDate;
        if (ev.EndDate > end) end = ev.EndDate;
    }

    TL.timeline.MinDateTime = start.AddMonths(-2);
    TL.timeline.MaxDateTime = end.AddMonths(2);
    TL.timeline.InitialDateTime = DateTime.Now;    

    TL.ReadyToAcceptData += new RoutedEventHandler(TL_ReadyToAcceptData);

    this.Container.Children.Clear();
this.Container.Children.Add(TL);
}

void TL_ReadyToAcceptData(object sender, RoutedEventArgs e)
{
TimelineUserControl TL = (TimelineUserControl)sender;
TL.timeline.ClearEvents();
TL.timeline.ResetEvents(events);
}

"TimelineUserControl" is a XAML class that defines that major parts of the timeline control (No C# code). "this.Container" is an empty Grid that will contain the created timeline object.

I've tried to use Loaded() as the "ReadyToAcceptData()" call, but it is called before OnControlLoaded() and hence before the timeline object is fully initialized. I need some other event to hook into; an event that is guaranteed to be fired after completion of the OnControlLoaded() method.

Or should I do this in some entirely different way?

Feb 27, 2010 at 1:19 PM

OK, finally got it! :-)

I should have hooked into the TL.timeline.Loaded() event instead of the TL.Loaded() event. So with :

...
 TL.timeline.Loaded+= new RoutedEventHandler(TL_timeline_Loaded);

...
void TL_timeline_Loaded(object sender, RoutedEventArgs e)
{
TimelineTray TLT = (TimelineTray)sender;
TLT.ClearEvents();
TLT.ResetEvents(events);
}

...everything works without any nullexception errors :-)

I still have the problem where the box around the mini events isn't correctly sized/positioned so this wasn't a side effect of the null exception...

Mar 3, 2010 at 2:14 AM

I am creating the timeline control in XAML rather than code-behind.  I hooked into the Loaded() event of the timeline, but still get nullexception errors when I attempt to call ClearEvents() and ResetEvents() from within the Loaded handler.  It appears as thought you have averted this problem by creating the control in the code-behind, but I'd prefer to use XAML where possible.  Any suggestions?

 

Coordinator
Mar 3, 2010 at 3:12 AM

can you attach a project with repro?

Mar 3, 2010 at 4:41 AM

I didn't see a way to attach a ZIP file.  Here is my XAML and code-behind.  The project was built in SL4 using VS2010 beta 2.  The timeline control was also converted to SL4.

<UserControl x:Class="TestTimeline.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:timeline="clr-namespace:TimelineLibrary;assembly=TimelineLibrary"
    Width="1000" Height="400">
    
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>

        <timeline:TimelineTray x:Name="Timeline" Grid.Row="0" Loaded="OnTimeLineTrayLoaded"
                               CalendarType="gregorian" HorizontalAlignment="Stretch" 
                               MinDateTime="01/01/2010" MaxDateTime="12/31/2010" InitialDateTime="01/01/2010">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="50" />
            </Grid.RowDefinitions>

            <timeline:TimelineBand Grid.Row="0" IsMainBand="True" ItemSourceType="months" HorizontalAlignment="Stretch"
                                   TimelineWindowSize="20" MaxEventHeight="130" />
            <timeline:TimelineBand Grid.Row="1" HorizontalAlignment="Stretch" ItemSourceType="years"   
                                   TimelineWindowSize="16" MaxEventHeight="4" />
        </timeline:TimelineTray>

        <Button Grid.Row="1" Height="20" Width="80" HorizontalAlignment="Left" Content="ResetEvents" Click="OnResetEventsClick" />
        <Button Grid.Row="1" Height="20" Width="80" Content="Clear Events" Click="OnClearEventsClick" />
    </Grid>
</UserControl>

using System.Windows;
using System.Windows.Controls;
using System.Xml.Linq;

namespace TestTimeline
{
    public partial class MainPage : UserControl
    {
        private XElement _events;

        public MainPage()
        {
            InitializeComponent();

            Buildevents();
        }

        private void OnTimelineLoaded(object sender, RoutedEventArgs e)
        {
            LoadEvents();
        }

        private void OnResetEventsClick(object sender, RoutedEventArgs e)
        {
            LoadEvents();
        }

        private void OnClearEventsClick(object sender, RoutedEventArgs e)
        {
            this.Timeline.ClearEvents();
        }

        private void Buildevents()
        {
            _events = new XElement("data");

            _events.Add(new XElement("event",
                new XAttribute("title", "Title 1"),
                new XAttribute("start", "2/18/2010"),
                new XAttribute("isDuration", false),
                new XAttribute("color", "Red"),
                "This is Event 1"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 2"),
               new XAttribute("start", "3/18/2010"),
               new XAttribute("end", "4/21/2010"),
               new XAttribute("isDuration", true),
               new XAttribute("color", "Blue"),
                "This is event 2"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 3"),
               new XAttribute("start", "4/1/2010"),
               new XAttribute("isDuration", false),
               new XAttribute("color", "Blue"),
                "This is event 3"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 4"),
               new XAttribute("start", "4/10/2010"),
               new XAttribute("isDuration", false),
               new XAttribute("color", "Blue"),
                "This is event 4"));
        }

        private void LoadEvents()
        {
            this.Timeline.ClearEvents();
            this.Timeline.ResetEvents(XDocument.Parse(_events.ToString()));
        }

        private void OnTimeLineTrayLoaded(object sender, RoutedEventArgs e)
        {
            LoadEvents();
        }
    }
}

 

Coordinator
Mar 3, 2010 at 6:51 PM

I've added new event that should help you: TimelineReady (please get it from sources). You should be able to run your code as this:

 

namespace TestTimeline
{
    public partial class MainPage : UserControl
    {
        private XElement _events;

        public MainPage()
        {
            InitializeComponent();
            Timeline.TimelineReady += new EventHandler(Timeline_TimelineReady);
        }

        void Timeline_TimelineReady(object sender, EventArgs e)
        {
            Buildevents();
            LoadEvents();
        }

        private void Buildevents()
        {
            _events = new XElement("data");

            _events.Add(new XElement("event",
                new XAttribute("title", "Title 1"),
                new XAttribute("start", "2/18/2010"),
                new XAttribute("isDuration", false),
                new XAttribute("color", "Red"),
                "This is Event 1"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 2"),
               new XAttribute("start", "3/18/2010"),
               new XAttribute("end", "4/21/2010"),
               new XAttribute("isDuration", true),
               new XAttribute("color", "Blue"),
                "This is event 2"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 3"),
               new XAttribute("start", "4/1/2010"),
               new XAttribute("isDuration", false),
               new XAttribute("color", "Blue"),
                "This is event 3"));

            _events.Add(new XElement("event",
               new XAttribute("title", "Title 4"),
               new XAttribute("start", "4/10/2010"),
               new XAttribute("isDuration", false),
               new XAttribute("color", "Blue"),
                "This is event 4"));
        }

        private void LoadEvents()
        {
            this.Timeline.ClearEvents();
            this.Timeline.ResetEvents(XDocument.Parse(_events.ToString()));
        }
    }
}

Mar 4, 2010 at 4:10 AM

Thanks for the VERY fast turnaround on the fix.  All works well with the new event. However, interestingly enough, if I use the sample data I'm generating in the code-behind, the "event 3" does not show up on the timeline.  If I change the date from 4/1/2010 to a different date (i.e., 5/1/2010), it does show up on the timeline.  There appears to be an issue with the layout.  When you tested this with your new change, did you observe this behavior using my sample data?

Coordinator
Mar 4, 2010 at 5:09 AM

It is displayed, but almost completely overlaps with another event. You can change MaxEventHeight of main band from 130 to 80 to resolve the issue, or change size of main band.