Track Editor: Difference between revisions

From Maria GDK Wiki
Jump to navigation Jump to search
()
()
Line 1,200: Line 1,200:
For further details, see [[Track_history|Track history]]
For further details, see [[Track_history|Track history]]


==== Track history settings ====
==== Track history settings ====¨
 
[[File:tse_track_history_settings.png|none|frame|Connection management]]
 
* 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 info ====
==== Track history info ====

Revision as of 16:08, 23 October 2019

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

This page is under construction!


Note
  • For general description of track related info, see General track service information.
  • For this part you will need to include the TPG.Maria.TrackLayer NuGet package as a minimum.
  • 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:

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 your track service(s), and that 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:

. . .
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);

        RefreshTrackDisplay();
    }
}

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 add and remove track lists, and filter the displayed lists.

Track info management

Track display

  • 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:

. . .
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 && _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);
    }
}

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, _defPos, 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;
}

. . .

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();
    }
}
. . .
Run your application, and verify that you can add and remove tracks, and filter the displayed tracks.

Edit track information

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>


And the following to your view model:

. . .
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;
    }
}
. . .

Run your application, and verify that you can change the track properties, and add and modify track fields.

Track filtering by tag fields

Track filtering by tag and tag value

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

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; }
. . .

... and 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();
    }
}

. . .

internal void RefreshTrackDisplay()
{
    Tracks = _trackServiceEngine.GetAllTracks(
        SelectedTrackList, ActiveTrackIdFilter, TrackIdFilter, 
        ActiveTagFilter, TagFilterKey, TagFilterValue);

    . . .
    NotifyPropertyChanged(() => ActiveTagFilter);
    NotifyPropertyChanged(() => TagFilterKey);
    NotifyPropertyChanged(() => TagFilterValue);
}
Running your application, you should now be able to filter the tracks by tags and tag values.

Refreshing track info

Refreshing track info

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

We will now add a button for manual refresh - as well as a check box for auto-refresh.

  • Manual refresh and track editing should be disabled while auto-refresh is active.

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

With handling in the view model:

public TrackEditorViewModel()
{
    . . .
    _refreshTimer = new Timer() { Interval = 1000 };
    _refreshTimer.Elapsed += OnRefreshTimer;
    _refreshTimer.Start();
   . . .
}

. . .
public ICommand TrackRefreshCmd { get { return new DelegateCommand(DoTrackRefresh, CanDoTrackRefresh); } }
. . .
public bool EditableTrack { get { return ValidTrack && !IsAutoRefresh; } }
. . .
public bool IsAutoRefresh
{
    get { return _isAutoRefresh; }
    set
    {
        _isAutoRefresh = value;

        NotifyPropertyChanged(() => IsAutoRefresh);
        NotifyPropertyChanged(() => EditableTrack);
    }
}
. . .
private void DoTrackRefresh(object obj)
{
    RefreshSelectedTrackInfo();
}

private bool CanDoTrackRefresh(object obj)
{
    return !IsAutoRefresh;
}
. . .
private void RefreshSelectedTrackInfo()
{
    var refreshedResult = _trackServiceEngine.GetTrackData(SelectedTrackList, SelectedTrack.TrackItemId.InstanceId);
    if (refreshedResult != null && refreshedResult.Any())
    {
        SelectedTrack = refreshedResult.FirstOrDefault();
    }
}
. . .
private void OnRefreshTimer(object sender, ElapsedEventArgs e)
{
    if (IsAutoRefresh)
    {
        RefreshSelectedTrackInfo();
    }
}
. . .
Running your application, selected track is updated with external changes when refreshed, e.g. by running a second instance of the track editor, or running the Globe Client with auto update.

Track history

Track history overview

The track history settings is defined for each track list by the ITrackHistoryOptions interface.

  • ChunkEntryCount
. . .
  • PosResolutionCm
  • TimeResolutionMs
. . .
  • 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 ====¨

Connection management
  • 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 info