Hi Yana,
Thanks for that. I refactored my code to use the TreeView control but unfortunately I ran into some issues with it. I modified my page as follows;
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
xmlns:viewModels
=
"clr-namespace:SrxRunSheet.ViewModels"
xmlns:telerikListView
=
"clr-namespace:Telerik.XamarinForms.DataControls.ListView;assembly=Telerik.XamarinForms.DataControls"
xmlns:telerikDataControls
=
"clr-namespace:Telerik.XamarinForms.DataControls;assembly=Telerik.XamarinForms.DataControls"
xmlns:telerikCommands
=
"clr-namespace:Telerik.XamarinForms.DataControls.ListView.Commands;assembly=Telerik.XamarinForms.DataControls"
x:Class
=
"SrxRunSheet.Views.RunSheet"
>
<
ContentPage.Content
>
<
Grid
RowSpacing
=
"0"
ColumnSpacing
=
"0"
BackgroundColor
=
"White"
>
<!--{StaticResource DarkSlateGrey}-->
<
Grid.RowDefinitions
>
<
RowDefinition
Height
=
"auto"
/>
<
RowDefinition
Height
=
"*"
/>
<
RowDefinition
Height
=
"50"
/>
</
Grid.RowDefinitions
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
Width
=
"auto"
/>
<
ColumnDefinition
Width
=
"*"
/>
<
ColumnDefinition
Width
=
"auto"
/>
</
Grid.ColumnDefinitions
>
<!-- Page Content -->
<
telerikDataControls:RadTreeView
x:Name
=
"treeView"
Grid.Row
=
"1"
Grid.Column
=
"0"
Grid.ColumnSpan
=
"3"
ItemsSource
=
"{Binding RunList}"
>
<!--<telerikDataControls:TreeViewDescriptor DisplayMemberPath="RunStart" TargetType="{x:Type viewModels:DriverRunVm}" />-->
<
telerikDataControls:TreeViewDescriptor
DisplayMemberPath
=
"RunStart"
TargetType
=
"{x:Type viewModels:DriverRunVm}"
ItemsSourcePath
=
"Jobs"
/>
<
telerikDataControls:TreeViewDescriptor
DisplayMemberPath
=
"JobLine1"
TargetType
=
"{x:Type viewModels:JobVm}"
/>
</
telerikDataControls:RadTreeView
>
</
Grid
>
</
ContentPage.Content
>
</
ContentPage
>
The code to populate the models is;
private
async
void
DisplayRun(DateTime runDt)
{
try
{
// Get the list of run IDs applicable to the run date...
List<
string
> runIds =
new
List<
string
>();
foreach
(var run
in
driverRuns)
{
if
((run.RunStart.ToLocalTime().Date == runDt.Date || run.RunFinish.ToLocalTime().Date == runDt)
|| (run.RunStart.ToLocalTime().Date < runDt.Date && run.RunFinish.ToLocalTime().Date > runDt.Date))
{
if
(!runIds.Contains(run.Id)) runIds.Add(run.Id);
}
}
if
(runIds.Count() == 0)
throw
new
Exception($
"Failed to locate any run records that include any jobs for {runDt.ToString("
ddd, d MMM yyyy
")}"
);
var runs = driverRuns.Where(x => runIds.Contains(x.Id)).OrderBy(x => x.RunStart);
var jobResponseService =
new
JobResponseService(
this
.realm);
var currentRunId =
string
.Empty;
DriverRunVm driverRun =
null
;
RunList =
new
ObservableCollection<DriverRunVm>();
foreach
(var run
in
runs.OrderBy(x => x.RunStart))
{
if
(currentRunId != run.Id)
{
if
(currentRunId.HasValue()) RunList.Add(driverRun);
driverRun =
new
DriverRunVm(run);
currentRunId = run.Id;
}
int
count = 0;
foreach
(var jobItem
in
run.Jobs.OrderBy(x => x.Sequence))
{
var job =
new
JobVm(jobItem)
{
DriverId = driver.Id,
RunId = run.Id
};
// Get the last response record for this job...
var response = jobResponseService.GetLastByJobId(job.Id);
if
(response !=
null
&& job.DriverId == response.DriverId && job.RunId == response.RunId)
{
job.Completed = response.Completed;
job.DriverNote = response.DriverNote;
job.Reason = response.Reason;
job.ReasonId = response.ReasonId;
}
driverRun.Jobs.Add(job);
count++;
if
(count >= 1)
break
;
}
}
RunList.Add(driverRun);
RunDate = runDt;
}
catch
(Exception ex)
{
await DisplayAlert(
"Display Run Sheet"
, ex.Message,
"OK"
);
return
;
}
}
And the classes are;
namespace
SrxRunSheet.ViewModels
public
class
DriverRunVm : ObservableCollection<JobVm>, INotifyPropertyChanged
{
private
readonly
Run run;
public
DriverRunVm(Run run)
{
this
.run = run;
run.PropertyChanged += PropertyUpdated;
}
public
ObservableCollection<JobVm> Jobs =
new
ObservableCollection<JobVm>();
public
string
RunId {
get
{
return
run.Id; } }
public
DateTime RunStartDt {
get
{
return
run.RunStart.UtcDateTime.ToLocalTime(); } }
public
string
RunStart {
get
{
return
FormatDate(RunStartDt); } }
public
DateTime RunFinishDt {
get
{
return
run.RunFinish.UtcDateTime.ToLocalTime(); } }
public
string
RunFinish {
get
{
return
FormatDate(RunFinishDt); } }
public
string
RunHeader {
get
{
return
$
"{RunStart} - {RunFinish}"
; } }
private
string
FormatDate(DateTime value)
{
return
$
"{value.ToString("
ddd, d MMM yyyy
").Replace("
.
", "
")} - {value.ToString("
h:mmtt
").ToLower()}"
;
}
private
void
PropertyUpdated(
object
sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e);
}
}
namespace
SrxRunSheet.ViewModels
{
public
class
JobVm : INotifyPropertyChanged
{
private
bool
isExpanded;
private
string
runId;
private
string
driverId;
private
readonly
Job job;
public
event
PropertyChangedEventHandler PropertyChanged;
public
JobVm(Job job)
{
this
.job = job;
this
.job.PropertyChanged += PropertyUpdated;
}
public
string
CAN {
get
{
return
job.CAN; } }
public
string
ClientName {
get
{
return
job.ClientName.Replace(
" "
,
" "
); } }
public
string
ContactName {
get
{
return
job.ContactName; } }
public
string
ContactPhone {
get
{
return
job.ContactPhone; } }
public
string
ContactDetail {
get
{
return
SetContactDetail(); } }
public
string
Description {
get
{
return
SetDescription(); } }
public
string
FlatAddress {
get
{
return
job.JobAddress.Replace(
"|"
,
" "
); } }
public
string
Id {
get
{
return
job.JobId; } }
public
string
Instructions {
get
{
return
job.Instructions; } }
public
string
JobAddress {
get
{
return
job.JobAddress.Replace(
"|"
, Environment.NewLine); } }
public
DateTime JobDate {
get
{
return
job.JobDate.UtcDateTime.ToLocalTime(); } }
public
string
RequestedBy {
get
{
return
job.RequestedBy; } }
public
Run Run {
get
{
return
job.Run; } }
public
string
Reason {
get
;
set
; }
public
int
? ReasonId {
get
;
set
; }
public
int
? Serviced {
get
;
set
; }
public
float
? Weight {
get
;
set
; }
public
string
JobLine1
{
get
{
if
(job.JobLine1.HasValue())
return
job.JobLine1
.Replace(
" "
,
" "
)
.Strip(Environment.NewLine, StringExtensions.StripFrom.End);
return
ClientName;
}
}
public
string
DriverId
{
get
{
return
driverId; }
set
{
if
(driverId != value)
{
driverId = value;
PropertyUpdated(nameof(DriverId));
}
}
}
public
string
JobIcon
{
get
{
if
(IsExpanded)
return
FontAwesome.Solid.Chevron_Circle_Up;
else
return
FontAwesome.Solid.Chevron_Circle_Down;
}
}
public
bool
IsExpanded
{
get
{
return
isExpanded; }
set
{
if
(isExpanded != value)
{
isExpanded = value;
PropertyUpdated(nameof(IsExpanded));
}
}
}
public
string
RunId
{
get
{
return
runId; }
set
{
if
(runId != value)
{
runId = value;
PropertyUpdated(nameof(RunId));
}
}
}
public
int
Sequence
{
get
{
return
job.Sequence; }
set
{
job.Sequence = value;
PropertyUpdated(nameof(Sequence));
}
}
public
string
SequenceNumber
{
get
{
return
Sequence.ToString().Trim(); }
}
private
string
SetContactDetail()
{
StringBuilder contact =
new
StringBuilder();
if
(job.ContactName.HasValue()) contact.AppendLine(job.ContactName);
if
(job.ContactPhone.HasValue()) contact.AppendLine(job.ContactPhone);
if
(contact.Length == 0)
return
""
;
return
(contact.ToString().Strip(Environment.NewLine, StringExtensions.StripFrom.End));
}
private
void
PropertyUpdated(
string
propertyName)
{
OnPropertyChanged(propertyName);
switch
(propertyName)
{
case
nameof(ContactName):
case
nameof(ContactPhone):
OnPropertyChanged(nameof(ContactDetail));
break
;
case
nameof(Completed):
OnPropertyChanged(nameof(JobStatus));
break
;
case
nameof(IsExpanded):
OnPropertyChanged(nameof(JobIcon));
break
;
}
}
private
void
PropertyUpdated(
object
sender, PropertyChangedEventArgs e)
{
PropertyUpdated(e.PropertyName);
}
private
string
SetDescription()
{
string
description =
"Error: No services have been included!"
;
if
(Services.Count == 1)
{
var service = Services.First();
description = $
"{service.Booked} x {service.Description}"
;
}
else
if
(Services.Count > 1)
{
description =
"Multiple services"
;
}
return
description;
}
protected
virtual
void
OnPropertyChanged(
string
propertyName)
{
Trace.WriteLine($
"{DateTime.Now.ToString("
hh:mm:ss.fff
")} := Property updated - {propertyName}"
);
PropertyChanged?.Invoke(
this
,
new
PropertyChangedEventArgs(propertyName));
}
}
}
When I run the application crashes with the error "System.NullReferenceException: 'Object reference not set to an instance of an object.'"
I i modify the page to use the following code the page displays a single record as expected;
<
telerikDataControls:RadTreeView
x:Name
=
"treeView"
Grid.Row
=
"1"
Grid.Column
=
"0"
Grid.ColumnSpan
=
"3"
ItemsSource
=
"{Binding RunList}"
>
<
telerikDataControls:TreeViewDescriptor
DisplayMemberPath
=
"RunStart"
TargetType
=
"{x:Type viewModels:DriverRunVm}"
/>
</
telerikDataControls:RadTreeView
>