Track Editor

From Maria GDK Wiki
Jump to navigation Jump to search
Maria track editor (WPF)

This section describes how to create a WPF application interacting with a Maria Track Service, without using MariaUserControl and track layer.

General

  • For general description of track related info, see General track service information.
  • You will need to include following NuGet packages:
  • You also need to have a Track Service available.
  • Sample code for this section is the MariaTrackEditor project, in the Sample Projects solution.

Start with creating a WPF App project!

  • Add a view model class, e.g. TrackEditorViewModel - inheriting ViewModelBase
  • Set the view model class to be the DataContext for your application.

Track service engine

The Track service engine encapsulates service interaction.

Available functions:

Connection
  • ConnectToTrackService
Connect to specified track service.
  • If URI is not given, endpoint info from configuration file will be used (if available).
Binding type is assumed to be BasicHttp!
  • Disconnect
Disconnect from service
  • IsConnected
Get current connection status.
Track lists
  • GetTrackLists
Retreive available tracklists from track service.
  • AddTrackList
Create a new track list.
  • RemoveTrackList
Remove track list, and all track info, if any.
Tracks
  • GetAllTracks
Retreive available tracks from a specific track list, matching the search criteria.
  • GetTrackData
Retreive specified tracks from a specific track list.
  • AddOrUpdateTrack
Create or update specific track.
Track history setting
  • GetTrackHistoryOptions
  • SetDefaultTrackHistoryOptions
Set default track history options for new track lists.
  • SetTrackHistoryOptions
Retreive track history options for specified track list.
Track history
  • RemoveTrack
Remove specified track from specific track list
  • GetTrackHistory
Retreive available track history information for specified track & track list, according to filter criteria.


Source code for MariaTrackServiceEngine

Track editor

Connection management

Connection features:

  • Connect to track service
  • Default track service from configuration
  • Alternative track service
  • Disconnect from service
  • Show connection status


Connection management


At startup, the Track Editor should try to connect to the latest track service successfully connected to.
To store the last value used, add a string value, StrLastUri to the project settings.


Adding project settings


Add the following to your window xaml:

<GroupBox Header="Connection" >
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Label Content="Service URI"/>
        <TextBox Grid.Row="0" Grid.Column="1" Margin="2"
                 Text="{Binding ConnectionUri}"/>
        <Button Grid.Row="0" Grid.Column="3" Height="22" Margin="2" 
                Content="Connect"
                Command="{Binding ConnectCmd}" />
        <Label Grid.Row="1" Grid.Column="0" Margin="2"
               Content="Status"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Margin="2"
                   VerticalAlignment="Center"
                   Text="{Binding ConnectionStatus}"/>
        <Button Grid.Row="1" Grid.Column="3" Height="22" Margin="2"
                Content="Disconnect" 
                Command="{Binding DisconnectCmd}"/>
    </Grid>
</GroupBox>

Then, add the following to your view model:

private string _connectionUri;
private string _connectionStatus;
MariaTrackServiceEngine _trackServiceEngine;

public TrackEditorViewModel()
{
    _trackServiceEngine = new MariaTrackServiceEngine();

    ConnectionUri = Settings.Default.StrLastUri;
    Connect();
}

. . .
public ICommand ConnectCmd { get { return new DelegateCommand(Connect, CanConnect); } }
public ICommand DisconnectCmd { get { return new DelegateCommand(Disconnect, CanDisconnect); } }
. . .

private void Connect(object obj = null)
{
    ConnectionStatus = "Connection requested ... ";

    if (_trackServiceEngine.ConnectToTrackService(ref _connectionUri))
    {
        Settings.Default.StrLastUri = ConnectionUri;
        Settings.Default.Save();

        ConnectionStatus = "Connected!";
    }
    else 
    {
        ConnectionStatus = "Not connected, connection failed! ";

        if (string.IsNullOrWhiteSpace(ConnectionUri))
        {
            ConnectionStatus += "\nSupply URI - or correct 'system.serviceModel' configuration!";
        }
    }

    RefreshConnectionInfo();
}

private bool CanConnect(object obj)
{
    return !_trackServiceEngine.IsConnected();
}

private void Disconnect(object obj)
{
    _trackServiceEngine.Disconnect();

    ConnectionStatus = "Disconnected!";
}

private bool CanDisconnect(object obj)
{
    return  _trackServiceEngine.IsConnected(); 
}
. . .
public string ConnectionUri {
    get { return _connectionUri; }
    set
    {
        _connectionUri = value;
        NotifyPropertyChanged(() => ConnectionUri);
    }
}

public string ConnectionStatus
{
    get { return _connectionStatus; }
    set
    {
        _connectionStatus = value;
        NotifyPropertyChanged(() => ConnectionStatus);
    }
}      
. . .
internal void RefreshConnectionInfo()
{
    NotifyPropertyChanged(() => ConnectionStatus);
    NotifyPropertyChanged(() => ConnectionUri);
}
. . .
Run your application, and verify that
  • you can connect to desired track service by specifying the URI.
  • you can disconnect from the service
  • the last successful service URI is used when restarting the application.

Track list management

Track list features:

  • Display existing track lists
  • Filtered view
  • Add new track list
  • Select track list
  • Remove selected track list


Track list management


Add the following to your window xaml:

<GroupBox Header="Track lists" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        
        <Label Grid.Row="0" Grid.Column="0" Margin="2"
               Content="New list"/>
        <TextBox Grid.Row="0" Grid.Column="1" Margin="2"
                 Text="{Binding NewTrackList}"
                 TextChanged="NewTrackListChanged"/>

        <CheckBox Grid.Row="1" Grid.Column="0" Margin="2"
                  Content="Filter" VerticalAlignment="Center" 
                  IsChecked="{Binding ActiveFilter}" />
        <TextBox Grid.Row="1" Grid.Column="1" Margin="2" 
                 Text="{Binding TrackListFilter}" 
                 VerticalAlignment="Center"
                 TextChanged="FilterChanged" />

        <StackPanel Grid.Row="2" Grid.Column="0" >
            <Label Margin="2"
                   Content="Track lists"/>
            <StackPanel Orientation="Horizontal" Margin="2">
                <Label Content="#"/>
                <Label Content="{Binding TrackLists.Count}"/>
            </StackPanel>
        </StackPanel>
        
        <ListBox Grid.Row="2" Grid.Column="1" Margin="2"
                 VerticalAlignment="Top"
                 ItemsSource="{Binding TrackLists}"
                 SelectedItem="{Binding SelectedTrackList}"/>
        
        <Button Grid.Row="0" Grid.Column="3" Height="22" Margin="2" 
                Content="Add"
                Command="{Binding AddTrackListCmd}"/>
        <Button Grid.Row="2" Grid.Column="3" Height="22" Margin="2" 
                VerticalAlignment="Top"
                Content="Remove"
                Command="{Binding RemoveTrackListCmd}"/>
    </Grid>
</GroupBox >

Then, add the following to your view model:

private string _newTrackList;
private string _trackListFilter;
private bool _activeFilter;
private string _selectedTrackList;

public ICommand AddTrackListCmd { get { return new DelegateCommand(AddTrackList, CanAddTrackList); } }
public ICommand RemoveTrackListCmd { get { return new DelegateCommand(RemoveTrackList, CanRemoveTrackList); } }

private void AddTrackList(object obj)
{
    _trackServiceEngine.AddTrackList(NewTrackList) ;

    ActiveFilter = false;
    SelectedTrackList = NewTrackList;
    RefreshTrackListDisplay();
}

private bool CanAddTrackList(object obj)
{
    return !string.IsNullOrWhiteSpace(NewTrackList) && TrackLists != null && !TrackLists.Contains(NewTrackList);
}
private void RemoveTrackList(object obj)
{
    _trackServiceEngine.RemoveTrackList(SelectedTrackList);

    RefreshTrackListDisplay();
}

private bool CanRemoveTrackList(object obj)
{
    return SelectedTrackList != null;
}

public string NewTrackList
{
    get { return _newTrackList; }
    set
    {
        _newTrackList = value;
        NotifyPropertyChanged(() => NewTrackList);
    }
}

public string TrackListFilter
{
    get { return _trackListFilter; }
    set
    {
        _trackListFilter = value;
        NotifyPropertyChanged(() => TrackListFilter);
    }
}

public bool ActiveFilter
{
    get { return _activeFilter; }
    set
    {
        _activeFilter = value;
        RefreshTrackListDisplay();
    }
}

public string SelectedTrackList
{
    get { return _selectedTrackList; }
    set
    {
        _selectedTrackList = value;
        NotifyPropertyChanged(() => SelectedTrackList);
    }
}

public List<string> TrackLists { get { return _trackServiceEngine.GetTrackLists(ActiveFilter, TrackListFilter); } }

internal void RefreshTrackListDisplay()
{
    NotifyPropertyChanged(() => NewTrackList);
    NotifyPropertyChanged(() => ActiveFilter);
    NotifyPropertyChanged(() => TrackListFilter);
    NotifyPropertyChanged(() => SelectedTrackList);
    NotifyPropertyChanged(() => TrackLists);
}

To handle the text changed events for the text boxes, add the following event handlers to the windows "Code behind"

TrackEditorViewModel _vm;

public MainWindow()
{
    InitializeComponent();
    
    _vm =new TrackEditorViewModel();
    DataContext = _vm;
}

private void NewTrackListChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.NewTrackList = textbox.Text;
        _vm.RefreshTrackListDisplay();
    }
}

private void FilterChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.TrackListFilter = textbox.Text;
        _vm.RefreshTrackListDisplay();
    }
}

To prevent old information in the view after disconnect, add refresh of the track list display when disconnecting.

. . .
private void Disconnect(object obj)
{
    _trackServiceEngine.Disconnect();
    ConnectionStatus = "Disconnected!";

    RefreshTrackListDisplay();
}
. . .
Run your application, and verify that you can
  • create a new tracklist
  • remove selected track list
  • filter the displayed lists according to track list id (name).

Track info management

Track display

Track display features:

  • Display existing tracks in the selected list
  • Filtered view
  • Add new track
  • Select track
  • Remove selected track


Track management

Add the following to your window xaml:

<GroupBox Header="Tracks" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Margin="2"
               Content="New track"/>
        <TextBox Grid.Row="0" Grid.Column="1" Margin="2" Height="22"
                 Text="{Binding NewTrack}"
                 TextChanged="NewTrackChanged"/>

        <CheckBox Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" 
                  Content="Id filter" 
                  IsChecked="{Binding ActiveTrackIdFilter}"/>

        <TextBox Grid.Row="1" Grid.Column="1" Margin="2" Height="22"
                 VerticalAlignment="Center"
                 Text="{Binding TrackIdFilter}"                          
                 TextChanged="TrackIdFilterChanged" />

        <StackPanel Grid.Row="3" Grid.Column="0" >
            <Label Margin="2"
                   Content="Tracks"/>
            <StackPanel Orientation="Horizontal" Margin="2">
                <Label Content="#"/>
                <Label Content="{Binding Tracks.Count}"/>
            </StackPanel>
        </StackPanel> 
        
        <ListBox Grid.Row="3" Grid.Column="1" MinHeight="22"  Margin="2"
                 SelectedItem="{Binding SelectedTrack}"
                 ItemsSource="{Binding Tracks}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Width="Auto" Height="Auto" Margin="0"
                               Text="{Binding Path=TrackItemId.InstanceId}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <Button Grid.Row="0" Grid.Column="3" Height="22" Margin="2" 
                Content="Add"
                Command="{Binding AddTrackCmd}"/>
        <Button Grid.Row="1" Grid.Column="3" Height="22" Margin="2" 
                Content="Remove"
                Command="{Binding RemoveTrackCmd}"/>
    </Grid>
</GroupBox>


Then, add the following to your view model:

private ITrackData _selectedTrack;
private bool _activeTrackIdFilter;

public ICommand AddTrackCmd { get { return new DelegateCommand(AddTrack, CanAddTrack); } }
public ICommand RemoveTrackCmd { get { return new DelegateCommand(RemoveTrack, CanRemoveTrack); } }

public string NewTrack { get; set; }

public ITrackData SelectedTrack
{
    get { return _selectedTrack; }
    set
    {
        if (_selectedTrack != null)
        { 
            var trackResult = _trackServiceEngine.GetTrackData(SelectedTrackList, _selectedTrack.TrackItemId.InstanceId);
            _selectedTrack = trackResult.Length > 0 ? trackResult[0] : null;
        }

        _selectedTrack = value;

        NotifyPropertyChanged(() => SelectedTrack);
    }
}

public ITrackData[] Tracks { get; private set; }

public bool ActiveTrackIdFilter
{
    get { return _activeTrackIdFilter; }
    set
    {
        _activeTrackIdFilter = value;
        RefreshTrackDisplay();
    }
}

public string TrackIdFilter { get; set; }

private void AddTrack(object obj)
{
    UpdateTrack(NewTrack, new GeoPos(60, 10), 90.0,  5.0, DateTime.UtcNow, true);
    RefreshTrackDisplay();
}

private bool CanAddTrack(object obj)
{
    return SelectedTrackList != null && !string.IsNullOrWhiteSpace(NewTrack) && !ExistingTrack();
}

private bool ExistingTrack()
{
    foreach (var track in Tracks)
    {
        if (NewTrack == track.TrackItemId.InstanceId)
            return true;
    }
    return false;
}

private void RemoveTrack(object obj)
{
    _trackServiceEngine.RemoveTrack(SelectedTrackList, SelectedTrack.TrackItemId.InstanceId);

    RefreshTrackDisplay();
}

private bool CanRemoveTrack(object obj)
{
    return SelectedTrackList != null && SelectedTrack != null;
}

private void UpdateTrack(string instanceId, GeoPos pos, double? course, double? speed, DateTime dateTime, bool updateSelected)
{
    var track = new TrackData(new ItemId(SelectedTrackList, instanceId), pos, course, speed) { ObservationTime = dateTime };

    if (_trackServiceEngine.AddOrUpdateTrack(SelectedTrackList, track))
    {
        RefreshTrackDisplay();
    }

    if (updateSelected)
    {
        SelectedTrack = track;
    }
}

internal void RefreshTrackDisplay()
{
    Tracks = _trackServiceEngine.GetAllTracks(SelectedTrackList, ActiveTrackIdFilter, TrackIdFilter, false, "", "");

    NotifyPropertyChanged(() => NewTrack);
    NotifyPropertyChanged(() => SelectedTrack);
    NotifyPropertyChanged(() => Tracks);
    NotifyPropertyChanged(() => ActiveTrackIdFilter);
    NotifyPropertyChanged(() => TrackIdFilter);
}

Handle the text changed events for the text boxes in the "Code behind":

private void NewTrackChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.NewTrack = textbox.Text;
        _vm.RefreshTrackDisplay();
    }
}

private void TrackIdFilterChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.TrackIdFilter = textbox.Text;
        _vm.RefreshTrackDisplay();
    }
}


Important
Remember to update the SelectedTrackList set property in your view model with a call to RefreshTrackDisplay!


Run your application, and verify that
  • tracks are displayed according to selected track list.
  • tracks can be added to selected track list
  • selected track can be removed from selected track list
  • track display can be filtered according to track id.

Edit track information

Track edit features:

  • Modify track properties
  • Position
  • Course
  • Speed
  • Time stamp, edit current or use current time.
  • Display track fields - tag and value
  • Add new fields
  • Modify value for existing fields


display & edit track information


Add the following to your window xaml:

<GroupBox Header="Edit track" IsEnabled="{Binding ValidTrack}" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Margin="2"
               Content="Track id" />
        <TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="2" 
                   Text="{Binding CurrentId}"/>
        <Button Grid.Row="0" Grid.Column="3" Height="22" Margin="2,4" 
                Content="Save"
                Command="{Binding SaveTrackCmd}"/>

        <Label Grid.Row="1" Grid.Column="0" Margin="2"
               Content="Position" />
        <TextBox Grid.Row="1" Grid.Column="1" Margin="2"                         
                 Text="{Binding CurrentPos}"
                 TextChanged="PositionChanged"/>

        <Label Grid.Row="2" Grid.Column="0" Margin="2"
                Content="Course" />
        <TextBox Grid.Row="2" Grid.Column="1"  Margin="2"                         
                 Text="{Binding CurrentCourse}" TextAlignment="Right"
                 TextChanged="CourseChanged"/>
        <Label Grid.Row="3" Grid.Column="0" Margin="2"
                Content="Speed" />
        <TextBox Grid.Row="3" Grid.Column="1" Margin="2" 
                 Text="{Binding CurrentSpeed}" TextAlignment="Right"  
                 TextChanged="SpeedChanged"/>

        <Label Grid.Row="4" Grid.Column="0" Margin="2"
               Content="Time" />
        <xctk:DateTimePicker Grid.Row="4" Grid.Column="1" Margin="2"
                             Format="Custom" FormatString="yyyy.MM.dd HH:mm:ss" 
                             ShowButtonSpinner="False"                                     
                             Value="{Binding TimeStamp}"
                             IsEnabled="{Binding TimeEnabled}" TimeFormatString="HH:mm:ss" />

        <CheckBox Grid.Row="4" Grid.Column="2" VerticalAlignment="Center"
                  Content="Use current time"
                  IsChecked="{Binding UseCurrentTime}" Margin="0,8"/>

        <Label Grid.Row="5" Grid.Column="0" 
               Content="Available tags" />

        <ListView Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2" Margin="2"
                  HorizontalContentAlignment="Stretch"
                  SelectedItem="{Binding SelectedField}"
                  ItemsSource="{Binding Fields}"
                  SizeChanged="EqualSpaceListViewSizeChanged">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Tag" Width="auto" DisplayMemberBinding="{Binding Key}" />
                    <GridViewColumn Header="Value" Width="auto" DisplayMemberBinding="{Binding Value}" />
                </GridView>
            </ListView.View>
        </ListView>               

        <Label Grid.Row="6" Grid.Column="0" Margin="2"
               Content="Tag and value" />
        <TextBox Grid.Row="6" Grid.Column="1"  Margin="2"                         
                 Text="{Binding SelectedFieldKey}" 
                 TextChanged="SelectedFieldKeyChanged"/>
        <TextBox Grid.Row="6" Grid.Column="2" Margin="2" 
                 Text="{Binding SelectedFieldValue}"/>

        <Button Grid.Row="6" Grid.Column="3" Height="22" Margin="2,4" 
                Content="Add or update"
                Command="{Binding UpdateFieldsCmd}"/>
    </Grid>
</GroupBox>


The track field information, ITrackData.Fields, is a dictionary (keys & values).
To handle these values in a ListView, we need a helper to add a helper class, as the Dictionary class does not handle two-way-data-binding, see Stackoverflow article

Here is source code for the helper class, add it to your view model - or as a separate file.

public class FieldDefItem : ViewModelBase
{
    private string _key;
    private string _value;

    public FieldDefItem(KeyValuePair<string, string> pair)
    {
        Key = pair.Key;
        Value = pair.Value;
    }

    public FieldDefItem(string selectedFieldKey, string selectedFieldValue)
    {
        Key = selectedFieldKey;
        Value = selectedFieldValue;
    }

    public string Key
    {
        get { return _key; }
        set
        {
            _key = value;
            NotifyPropertyChanged(() => Key);
        }
    }
    public string Value
    {
        get { return _value; }
        set
        {
            _value = value;
            NotifyPropertyChanged(() => Value);
        }
    }
}

Now you can add the following to your view model class:

private bool _isDirty;
private bool _useCurrentTime;
private string _selectedFieldKey;
private string _selectedFieldValue;
private FieldDefItem _selectedField;

public ICommand UpdateFieldsCmd { get { return new DelegateCommand(AddOrUpdateField, CanAddOrUpdateField); } }
public ICommand SaveTrackCmd { get { return new DelegateCommand(SaveTrack, CanSaveTrack); } }

public bool ValidTrack { get { return SelectedTrack != null ; } }
public string CurrentId { get { return SelectedTrack?.TrackItemId.InstanceId; } }

public string CurrentPos
{
    get { return SelectedTrack != null && SelectedTrack.Pos.HasValue ? SelectedTrack.Pos.Value.ToString(PositionFormat.GeoDMS) : "-"; }
    set
    {
        if (SelectedTrack == null)
            return;

        GeoPos pos;
        if (GeoPos.TryParse(value, PositionFormat.GeoDMS, out pos))
        {
            SelectedTrack.Pos = pos;
            _isDirty = true;
        }
        NotifyPropertyChanged(() => CurrentPos);
    }
}

public string CurrentCourse
{
    get { return SelectedTrack != null && SelectedTrack.Course.HasValue ? SelectedTrack.Course.Value.ToString("N1") : "-"; }
    set
    {
        if (SelectedTrack == null)
            return;

        double course;
        if (double.TryParse(value, out course ))
        {
            SelectedTrack.Course = course;
            _isDirty = true;
        }
        NotifyPropertyChanged(() => CurrentCourse);
    }
}

public string CurrentSpeed
{
    get { return SelectedTrack != null && SelectedTrack.Speed.HasValue ? SelectedTrack.Speed.Value.ToString("N1") : "-"; }

    set
    {
        if (SelectedTrack == null)
            return;

        double speed;
        if (double.TryParse(value, out speed))
        {
            SelectedTrack.Speed = speed;
            _isDirty = true;
        }
        NotifyPropertyChanged(() => CurrentSpeed);
    }
}

public DateTime TimeStamp
{
    get { return SelectedTrack != null && SelectedTrack.ObservationTime.HasValue ? SelectedTrack.ObservationTime.Value : DateTime.MinValue; }
    set
    {
        if (SelectedTrack != null)
        {
            SelectedTrack.ObservationTime = value;
            _isDirty = true;
        }
        
        NotifyPropertyChanged(() => TimeStamp);
    }
}

public bool TimeEnabled { get { return SelectedTrack != null && !UseCurrentTime; } }

public bool UseCurrentTime
{
    get { return _useCurrentTime; }
    set
    {
        _useCurrentTime = value;
        _isDirty = true;
        RefreshTrackEditDisplay();
    }
}

public string SelectedFieldKey
{
    get { return !string.IsNullOrWhiteSpace(_selectedFieldKey) ? _selectedFieldKey : ""; }
    set
    {
        _selectedFieldKey = value;
        NotifyPropertyChanged(() => SelectedFieldKey);
    }
}

public string SelectedFieldValue
{
    get { return !string.IsNullOrWhiteSpace(_selectedFieldValue) ? _selectedFieldValue : ""; }
    set
    {
        _selectedFieldValue = value;
        NotifyPropertyChanged(() => SelectedFieldValue);
    }
}

public FieldDefItem SelectedField
{
    get { return _selectedField; }
    set
    {
        _selectedField = value;

        SelectedFieldKey = _selectedField?.Key;
        SelectedFieldValue = _selectedField?.Value;

        NotifyPropertyChanged(() => SelectedFieldKey);
    }
}

public ObservableCollection<FieldDefItem> Fields { get; private set; } 

private void AddOrUpdateField(object obj)
{
    var found = false;

    foreach (var field in Fields)
    {
        if (field.Key == SelectedFieldKey)
        {
            field.Value = SelectedFieldValue;
            found = true;
        }
    }

    if (!found)
        Fields.Add(new FieldDefItem(SelectedFieldKey, SelectedFieldValue));

    SelectedTrack.Fields[SelectedFieldKey] = SelectedFieldValue;
    _isDirty = true;
    RefreshTrackEditDisplay();
}

private bool CanAddOrUpdateField(object obj)
{
    return SelectedTrackList != null && SelectedTrack != null &&  !string.IsNullOrWhiteSpace(_selectedFieldKey);
}

private void SaveTrack(object obj)
{
    if (UseCurrentTime)
        SelectedTrack.ObservationTime = DateTime.UtcNow;

    _trackServiceEngine.AddOrUpdateTrack(SelectedTrackList, SelectedTrack);
    _isDirty = false;

    RefreshTrackEditDisplay();
}

private bool CanSaveTrack(object obj)
{
    return _isDirty || UseCurrentTime;
}

private void RefreshTrackEditDisplay()
{
    NotifyPropertyChanged(() => SelectedTrack);

    NotifyPropertyChanged(() => ValidTrack);
    NotifyPropertyChanged(() => UseCurrentTime);
    NotifyPropertyChanged(() => TimeEnabled);
    NotifyPropertyChanged(() => CurrentId);
    NotifyPropertyChanged(() => CurrentPos);
    NotifyPropertyChanged(() => CurrentSpeed);
    NotifyPropertyChanged(() => CurrentCourse);
    NotifyPropertyChanged(() => TimeStamp);

    NotifyPropertyChanged(() => Fields);
    NotifyPropertyChanged(() => SelectedField);
    NotifyPropertyChanged(() => SelectedFieldKey);
    NotifyPropertyChanged(() => SelectedFieldValue);
}


And again, handle the text changed events for the text boxes in the "Code behind":

private void SelectedFieldKeyChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.SelectedFieldKey = textbox.Text;
    }
}

private void PositionChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        GeoPos pos;                
        if (GeoPos.TryParse(textbox.Text, PositionFormat.GeoDMS, out pos))
        {
            _vm.CurrentPos = textbox.Text;
        }
        else
        {
            textbox.Text = _vm.CurrentPos;
        }
    }
}

private void CourseChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        double course;
        if (double.TryParse(textbox.Text, out course))
            _vm.CurrentCourse = textbox.Text;
        else
            textbox.Text = _vm.CurrentCourse ;
    }
}

private void SpeedChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        double speed;
        if (double.TryParse(textbox.Text, out speed))
            _vm.CurrentSpeed = textbox.Text;
        else
            textbox.Text = _vm.CurrentSpeed;
    }
}

private void EqualSpaceListViewSizeChanged(object sender, SizeChangedEventArgs e)
{
    ListView lv = sender as ListView;
    GridView gv = lv.View as GridView;
    var actualWidth = lv.ActualWidth - (SystemParameters.VerticalScrollBarWidth + 10); // Adjust for scrollbar and some extra... 

    if (gv.Columns.Count == 0)
        return;

    var deltaWidth = actualWidth / gv.Columns.Count;

    foreach (var  col in gv.Columns)
    {
        col.Width = deltaWidth;
    }
}


Now, in the view model, update the SelectedTrack property:

 
public ITrackData SelectedTrack
{
    get { return _selectedTrack; }
    set
    {
        if (_selectedTrack != null && _isDirty)
        {
            var trackResult = _trackServiceEngine.GetTrackData(SelectedTrackList, _selectedTrack.TrackItemId.InstanceId);
            _selectedTrack = trackResult.Length > 0 ? trackResult[0] : null;
        }

        _selectedTrack = value;

        SelectedField = null;
        Fields = new ObservableCollection<FieldDefItem>();

        if (SelectedTrack != null)
        {
            foreach (var pair in SelectedTrack.Fields)
            {
                Fields.Add(new FieldDefItem(pair));
            }
        }

        NotifyPropertyChanged(() => SelectedTrack);
        RefreshTrackEditDisplay();
        _isDirty = false;
    }
}
Run your application, and verify that you can modify and save changes
  • Track properties
  • Position
  • Course
  • Speed
  • Time - modified and current time
  • Track fields
  • Add new fields
  • Modify value for existing fields

Track filtering by tag fields

Now that we can add and display track fields, we will add filtering by tag field and value.


Track filtering by tag and tag value


Add the following fields to the Tracks group box, beneath the Id filter:

<CheckBox Grid.Row="2" Grid.Column="0"
          VerticalAlignment="Center"
          Content="Tag filter"  
          IsChecked="{Binding ActiveTagFilter}"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Vertical" Margin="2" >
    <StackPanel Orientation="Horizontal">
        <Label Content="Tag" VerticalAlignment="Center"/>
        <TextBox Width="60" Height="22" VerticalAlignment="Center"
                 Text="{Binding TagFilterKey}" 
                 TextChanged="TagFilterKeyChanged"/>
        <Label Content="Value" VerticalAlignment="Center"/>
        <TextBox  Width="120" Height="22" VerticalAlignment="Center"
                  Text="{Binding TagFilterValue}"
                  TextChanged="TagFilterValueChanged"/>
    </StackPanel>
</StackPanel>

With the following handling in the view model:

public bool ActiveTagFilter
{
    get { return _activeTagFilter; }
    set
    {
        _activeTagFilter = value;
        RefreshTrackDisplay();
    }
}

public string TagFilterKey { get; set; }
public string TagFilterValue { get; set; }


Update loading of tracks in RefreshTrackDisplay to include filtering, and refresh the new filter properties:

Tracks = _trackServiceEngine.GetAllTracks(
    SelectedTrackList, ActiveTrackIdFilter, TrackIdFilter,
    ActiveTagFilter, TagFilterKey, TagFilterValue);

NotifyPropertyChanged(() => ActiveTagFilter);
NotifyPropertyChanged(() => TagFilterKey);
NotifyPropertyChanged(() => TagFilterValue);

Then, in the "code behind":

private void TagFilterKeyChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.TagFilterKey = textbox.Text;
        _vm.RefreshTrackDisplay();
    }
}

private void TagFilterValueChanged(object sender, TextChangedEventArgs e)
{
    var textbox = sender as TextBox;
    if (textbox != null)
    {
        _vm.TagFilterValue = textbox.Text;
        _vm.RefreshTrackDisplay();
    }
}
Run your application, and verify that you are able to
  • filter the tracks by tag - no tag value
  • filter the tracks by tag and specific value.

Refreshing track info

As the track information may change while displayed, it will be be convenient to have a refresh option.

Add a button for manual refresh - and a check box for auto-refresh.

  • Auto refresh is triggered by timer event once every second.
  • Manual refresh and track editing should be disabled while auto-refresh is active.


Refreshing track info


Add the button and check box in the track management section, and change the IsEnabled binding for Edit track:

<GroupBox Header="Tracks" >
        . . .
        <Button Grid.Row="2" Grid.Column="3" Margin="2"
                Content="Refresh"
                Command="{Binding TrackRefreshCmd}"/>
        <CheckBox Grid.Row="3" Grid.Column="3" Margin="2"
                  Content="Auto refresh" 
                  IsChecked="{Binding IsAutoRefresh}"/>
        . . .
<GroupBox Header="Edit track" IsEnabled="{Binding EditableTrack}" >
. . .

In the view model:

  • Add timer handling in the view model constructor
  • Add necessary properties and command handlers for the refresh handling
  • Add refresh of EditableTrack in RefreshTrackEditDisplay
public TrackEditorViewModel()
{
    . . .
    _timer = new Timer() { Interval = 1000 };
    _timer.Elapsed += OnTimerTick;
    _timer.Start();
}

private Timer _timer;
private bool _isAutoRefresh;

private void OnTimerTick(object sender, ElapsedEventArgs e)
{
    if (IsAutoRefresh)
    {
        RefreshSelectedTrackInfo();
    }
}

public ICommand TrackRefreshCmd { get { return new DelegateCommand(DoTrackRefresh, CanDoTrackRefresh); } }

private void DoTrackRefresh(object obj)
{
    RefreshSelectedTrackInfo();
}

private bool CanDoTrackRefresh(object obj)
{
    return !IsAutoRefresh && ValidTrack;
}

public bool IsAutoRefresh
{
    get { return _isAutoRefresh; }
    set
    {
        _isAutoRefresh = value;

        NotifyPropertyChanged(() => IsAutoRefresh);
        NotifyPropertyChanged(() => EditableTrack);
    }
}

public bool EditableTrack { get { return ValidTrack && !IsAutoRefresh; } }

private void RefreshSelectedTrackInfo()
{
    var refreshedResult = _trackServiceEngine.GetTrackData(SelectedTrackList, SelectedTrack.TrackItemId.InstanceId);
    if (refreshedResult != null && refreshedResult.Any())
    {
        SelectedTrack = refreshedResult.FirstOrDefault();
    }
}

private void RefreshTrackEditDisplay()
{
    NotifyPropertyChanged(() => EditableTrack);
    . . .
}
Run your application, and verify that
  • Selected track is updated with external changes when refreshed.
  • Manual refresh and track editing are disabled while auto-refresh is active.

Create external changes by:

  • running a second instance of the track editor
  • running the Globe Client with auto update.

Track history

Track history overview

General

Track history settings are defined for each track list by the ITrackHistoryOptions interface.

  • ChunkEntryCount
Entries capacity.
  • if 0 or not supplied, track history setting is removed.
  • Typical value 128.
  • PosResolutionCm
Distance limit for storing historic info
  • if distance of movement less than the given limit, no information is collected.
  • if 0 or not supplied, minimum value 1 cm, is used.
  • Typical value 100 cm.
  • TimeResolutionMs
Time limit for storing historic info
  • if distance of movement less than the given limit, no information is collected.
  • if 0 or not supplied, minimum value 1 ms, is used.
  • Typical value 100 ms.
  • MaxAge
Obsolete - use HistoryTimeLimit filter value when requesting track history instead.
  • MaxCount
Obsolete - use HistoryElementLimit filter value when requesting track history instead.

Historic position info, defined by ITrackHistoryData and ITrackHistoryInfo.

For further details, see Track history

Track history settings

History settings features:

  • Display current track history setting for selected track list
  • Create initial track history setting for selected track list
  • Modify and update track history setting for selected track list
  • Clear track history setting for selected track list
  • Use the track history setting for selected track list as default for new track lists created in service
Note
  • Changing the history settings for a track list will affect all users of the list.
  • When the history settings are changed, all previous history information is cleared.


Track history settings


Add to your window xaml:

<GroupBox Header="Track history settings" >
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Content="Chunc entry cnt" />
        <xctk:IntegerUpDown Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch"
                            VerticalAlignment="Center" TextAlignment="Right"
                            Text="{Binding ChunkEntryCount}" 
                            ValueChanged="ChunkEntryCountChanged"
                            Minimum="0" Maximum="5000000"  
                            IsEnabled="{Binding HistorySettingsEnabled}" />

        <Label Grid.Row="1" Grid.Column="0" Content="Pos res. (cm)" />
        <xctk:IntegerUpDown Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch"
                            Text="{Binding PosResolution}"
                            ValueChanged="PosResolutionChanged"
                            Minimum="0" Maximum="5000000"                             
                            VerticalAlignment="Center" TextAlignment="Right"
                            IsEnabled="{Binding HistorySettingsEnabled}"  />
        
        <Label Grid.Row="2" Grid.Column="0" Content="Time res. (ms)" />
        <xctk:IntegerUpDown Grid.Row="2" Grid.Column="1" HorizontalAlignment="Stretch"
                            Text="{Binding TimeResolution}"
                            ValueChanged="TimeResolutionChanged"
                            Minimum="0" Maximum="5000000"  
                            VerticalAlignment="Center" TextAlignment="Right"                           
                            IsEnabled="{Binding HistorySettingsEnabled}" />

        <Button Grid.Row="0" Grid.Column="3" Height="22" Margin="2" 
                Content="Set as default"
                Command="{Binding DefaultHistorySettingsCmd}"/>
        <Button Grid.Row="1" Grid.Column="3" Height="22" Margin="2" 
                Content="Add setting"
                Command="{Binding AddHistorySettingsCmd}"/>
        <Button Grid.Row="2" Grid.Column="3" Height="22" Margin="2" 
                Content="Updpate setting"
                Command="{Binding SaveHistorySettingsCmd}"
                IsEnabled="{Binding HistorySettingsEnabled}" />
        <Button Grid.Row="3" Grid.Column="3" Height="22" Margin="2" 
                Content="Clear setting"
                Command="{Binding ResetHistorySettingsCmd}"
                IsEnabled="{Binding HistorySettingsEnabled}" />
    </Grid>
</GroupBox>


Then, add to your view model:

private bool _historySettingsDirty;

public ICommand SaveHistorySettingsCmd { get { return new DelegateCommand(SaveHistorySettings, CanSaveHistorySetings); } }
public ICommand ResetHistorySettingsCmd { get { return new DelegateCommand(ResetHistorySettings, CanResetHistorySettings); } }
public ICommand AddHistorySettingsCmd { get { return new DelegateCommand(AddHistorySettings, CanAddHistorySettings); } }
public ICommand DefaultHistorySettingsCmd { get { return new DelegateCommand(DefaultHistorySettings, CanDefaultHistorySettings); } }

public bool HistorySettingsEnabled { get { return CurrentTrackHistoryOptions != null && SelectedTrackList != null; } }

public ITrackHistoryOptions CurrentTrackHistoryOptions { get; set; }

public int? ChunkEntryCount
{
    get { return CurrentTrackHistoryOptions?.ChunkEntryCount; }
    set
    {
        if (CurrentTrackHistoryOptions != null)
        {
            CurrentTrackHistoryOptions.ChunkEntryCount = value;
            NotifyPropertyChanged(() => ChunkEntryCount);
            _historySettingsDirty = true;
        }
    }
}

public int? PosResolution
{
    get { return CurrentTrackHistoryOptions?.PosResolutionCm; }
    set
    {
        if (CurrentTrackHistoryOptions != null)
        {
            CurrentTrackHistoryOptions.PosResolutionCm = value;
            NotifyPropertyChanged(() => PosResolution);
            _historySettingsDirty = true;
        }
    }
}
public int? TimeResolution
{
    get { return CurrentTrackHistoryOptions?.TimeResolutionMs; }
    set
    {
        if (CurrentTrackHistoryOptions != null)
        {
            CurrentTrackHistoryOptions.TimeResolutionMs = value;
            NotifyPropertyChanged(() => TimeResolution);
            _historySettingsDirty = true;
        }
    }
}

private void SaveHistorySettings(object obj)
{
    _trackServiceEngine.SetTrackHistoryOptions(SelectedTrackList, CurrentTrackHistoryOptions);
    _historySettingsDirty = false;

    RefreshTrackHistorySettings();
}

private bool CanSaveHistorySetings(object obj)
{
    return HistorySettingsEnabled && _historySettingsDirty;
}

private void ResetHistorySettings(object obj)
{
    CurrentTrackHistoryOptions = null;
    _trackServiceEngine.SetTrackHistoryOptions(SelectedTrackList, null);
    _historySettingsDirty = false;
    RefreshTrackHistorySettings();
}

private bool CanResetHistorySettings(object obj)
{
    return HistorySettingsEnabled;
}

private void DefaultHistorySettings(object obj)
{
    _trackServiceEngine.SetDefaultTrackHistoryOptions(CurrentTrackHistoryOptions);
}

private bool CanDefaultHistorySettings(object obj)
{
    return true;
}

private void AddHistorySettings(object obj)
{
    CurrentTrackHistoryOptions = new TrackHistoryOptions { ChunkEntryCount = 128, PosResolutionCm = 100, TimeResolutionMs = 100 };

    _trackServiceEngine.SetTrackHistoryOptions(SelectedTrackList, CurrentTrackHistoryOptions);
    RefreshTrackHistorySettings();
    _historySettingsDirty = false;
}

private bool CanAddHistorySettings(object obj)
{
    return SelectedTrackList != null;
}

private void RefreshTrackHistorySettings()
{
    CurrentTrackHistoryOptions = _trackServiceEngine.GetTrackHistoryOptions(SelectedTrackList);

    NotifyPropertyChanged(() => ChunkEntryCount);
    NotifyPropertyChanged(() => PosResolution);
    NotifyPropertyChanged(() => TimeResolution);

    NotifyPropertyChanged(() => HistorySettingsEnabled);
}


Handle the text changed events for the text boxes in the "Code behind":

private void ChunkEntryCountChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var component = sender as IntegerUpDown;
    if (component != null)
    {
        int cec;
        if (int.TryParse(component.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out cec))
            _vm.ChunkEntryCount = cec;
        else
            component.Text = _vm.ChunkEntryCount.ToString();
    }
}

private void PosResolutionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var component = sender as IntegerUpDown;
    if (component != null)
    {
        int pr;
        if (int.TryParse(component.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out pr))
            _vm.PosResolution = pr;
        else
            component.Text = _vm.PosResolution.ToString();
    }
}

private void TimeResolutionChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var component = sender as IntegerUpDown;
    if (component != null)
    {
        int tr;
        if (int.TryParse(component.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out tr))
            _vm.TimeResolution = tr;
        else
            component.Text = _vm.TimeResolution.ToString();
    }
}


Important
Also remember to update the SelectedTrackList set property in your view model:
  • call RefreshTrackHistorySettings()
  • reset _historySettingsDirty (false)


Run your application, and verify that
  • current track history setting is displayed when track lists are selected
  • you can create initial track history setting for the selected track list
  • you can modify and update track history setting for selected track list
  • you can clear track history setting for selected track list
  • you can the track history setting for selected track list as default for new track lists

Track history info

Track history info features

  • Display history info for selected track
  • Supply age limitation for the history results. Hours, minutes or seconds.
  • Limit number of history results.
  • Additionally, display current date and time.


Track history info


Add to your window xaml:

<GroupBox Header="Track history" IsEnabled="{Binding ValidTrack}" >
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>

        <CheckBox Grid.Row="0" Grid.Column="0" Content="Limit age"
                  IsChecked="{Binding LimitAge}"
                  ToolTip="Seconds!"/>

        <xctk:IntegerUpDown  Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" Margin="2" VerticalAlignment="Top"
                             Text="{Binding MaxAge}" TextAlignment="Right" 
                             ValueChanged="MaxAgeChanged"                
                             Minimum="1" Maximum="1000" />

        <StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center" >
            <RadioButton Content="Hours"
                         IsChecked="{Binding IsHours}"/>
            <RadioButton Content="Minutes"
                         IsChecked="{Binding IsMinutes}"/>
            <RadioButton Content="Seconds"
                         IsChecked="{Binding IsSeconds}"/>
        </StackPanel>

        <CheckBox Grid.Row="1" Grid.Column="0" 
                  Content="Limit Count" 
                  IsChecked="{Binding LimitCount}"/>

        <xctk:IntegerUpDown Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" Margin="2"
                            Text="{Binding MaxCount}" TextAlignment="Right" 
                            ValueChanged="MaxCountChanged"
                            Minimum="1" Maximum="10000"/>

        <Label Grid.Row="2" Grid.Column="0" 
               Content="Historic info"/>

        <ListView Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Margin="2"
                  HorizontalContentAlignment="Stretch"
                  ItemsSource="{Binding TrackHistoryInfo}"
                  SizeChanged="EqualSpaceListViewSizeChanged"
                  IsSynchronizedWithCurrentItem="True">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Time" Width="120" DisplayMemberBinding="{Binding ObservationTime, StringFormat=yyyy.MM.dd HH:mm:ss}" />
                    <GridViewColumn Header="Position" Width="120" DisplayMemberBinding="{Binding Position}" />
                </GridView>
            </ListView.View>
        </ListView>

        <StackPanel Grid.Row="3" Grid.Column="0" Orientation="Vertical" >
            <StackPanel Grid.Row="3" Grid.Column="0" Orientation="Horizontal" >
                <Label Content="#"/>
                <Label Content="{Binding TrackHistoryInfo.Count}"/>
            </StackPanel>
            <TextBlock Text="{Binding CurrentDate}" FontWeight="Bold" Foreground="SaddleBrown"/>
            <TextBlock Text="{Binding CurrentTime}" FontWeight="Bold" Foreground="SaddleBrown"/>
        </StackPanel>
    </Grid>
</GroupBox>


Then, add to your view model:


private uint _maxAge;
private bool _limitAge;
private uint _maxCount;
private bool _limitCount;
private bool _isSeconds;
private bool _isMinutes;
private bool _isHours;

public string CurrentDate { get { return DateTime.UtcNow.ToString("yyyy.MM.dd"); } }
public string CurrentTime { get { return DateTime.UtcNow.ToString("HH:mm:ss"); }}

public uint MaxAge
{
    get { return _maxAge; }
    set
    {
        _maxAge = value;
        RefreshTrackHistory();
    }
}

public bool LimitAge
{
    get { return _limitAge; }
    set
    {
        _limitAge = value;
        RefreshTrackHistory();
    }
}

public bool IsSeconds
{
    get { return _isSeconds; }
    set
    {
        _isSeconds = value;
        _isMinutes = !value;
        _isHours = !value;
        RefreshTrackHistory();
    }
}

public bool IsMinutes
{
    get { return _isMinutes; }
    set
    {
        _isSeconds = !value;
        _isMinutes = value;
        _isHours = !value;
        RefreshTrackHistory();
    }
}

public bool IsHours
{
    get { return _isHours; }
    set
    {
        _isSeconds = !value;
        _isMinutes = !value;
        _isHours = value;
        RefreshTrackHistory();
    }
}

public uint MaxCount
{
    get { return _maxCount; }
    set
    {
        _maxCount = value;
        RefreshTrackHistory();
    }
}

public bool LimitCount
{
    get { return _limitCount; }
    set
    {
        _limitCount = value;
        RefreshTrackHistory();
    }
} 

public List<ITrackHistoryInfo> TrackHistoryInfo { get; private set; }

internal void RefreshTrackHistory()
{
    NotifyPropertyChanged(() => IsSeconds);
    NotifyPropertyChanged(() => IsMinutes);
    NotifyPropertyChanged(() => IsHours);
    NotifyPropertyChanged(() => LimitAge);
    NotifyPropertyChanged(() => MaxAge);
    NotifyPropertyChanged(() => LimitCount);
    NotifyPropertyChanged(() => MaxCount);

    TrackHistoryInfo = new List<ITrackHistoryInfo>();

    if (SelectedTrack != null)
    {
        var historyFilterSpec = new TrackHistoryFilterSpec();

        if (LimitCount) historyFilterSpec.HistoryElementLimit = MaxCount;
        if (LimitAge)
        {
            if (IsHours) historyFilterSpec.HistoryTimeLimit = TimeSpan.FromHours(MaxAge);
            if (IsMinutes) historyFilterSpec.HistoryTimeLimit = TimeSpan.FromMinutes(MaxAge);
            if (IsSeconds) historyFilterSpec.HistoryTimeLimit = TimeSpan.FromSeconds(MaxAge);
        }

        var history = _trackServiceEngine.GetTrackHistory(SelectedTrackList, historyFilterSpec, SelectedTrack.TrackItemId.InstanceId);
        if (history != null && history.Any())
        {
            var list = history[0].TrackHistoryInfos;
            list.Reverse();
            TrackHistoryInfo = list;                    
        }
    }

    NotifyPropertyChanged(() => TrackHistoryInfo);
}

And in the code behind:

private void MaxAgeChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var component = sender as IntegerUpDown;
    if (component != null)
    {
        uint ma;
        if (uint.TryParse(component.Text, out ma))
            _vm.MaxAge = ma;
        else
            component.Text = _vm.MaxAge.ToString();
    }
}

private void MaxCountChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var component = sender as IntegerUpDown;
    if (component != null)
    {
        uint mc;
        if (uint.TryParse(component.Text, out mc))
            _vm.MaxCount = mc;
        else
            component.Text = _vm.MaxCount.ToString();
    }
}
Important
Perform additional update of the track history info by calling RefreshTrackHistory() the in the existing code:
  • In RefreshTrackHistorySettings
  • In SelectedTrackList set{}
  • In SelectedTrack set{}
In the view model constructor, set initial values for:
  • IsSeconds = true
  • MaxAge = 10
  • MaxCount = 10
In the timer event handler
  • Refresh CurrentDate and CurrentTime


Run your application, and verify
that history info for selected track is displayed according to:
  • the current track list history settings.
  • time limitations
  • number limitations
Also verify that:
  • date and time are displayed correctly (Universal Time (UTC))
  • track history info is cleared when the history settings are changed
  • with high resolution value, e.g. 100,000 (1 km) and e.g. 60000 (1 minute), track history elements are not added until at least one of the limits are exceeded