I'm using Visual Studio 2015 and MVVM Light Toolkit to build a WPF app. When the user clicks an employee in a DataGrid
, we show the record's details to allow editing. This details area consists of two tabs: Demographics and Tests. The Tests tab displays a ListView
of the tests for this person.
Here's the structure:
MainWindow.xaml:
<DataTemplate x:Key="EmployeeSearchTemplate">
<view:EmployeeSearchView />
</DataTemplate>
<ContentControl ContentTemplate="{StaticResource EmployeeSearchTemplate}" />
EmployeeSearchView.xaml:
<UserControl.DataContext>
<viewModel:EmployeeSearchViewModel />
</UserControl.DataContext>
<ContentControl Content="{Binding SelectedEmployee}"
ContentTemplate="{StaticResource EmployeeViewTemplate}" .../>
When the user selects the Tests tab, we search the db and return the tests for this employee, if any.
EmployeeView.xaml:
<DataTemplate x:Key="TestsViewTemplate">
<views:TestsView />
</DataTemplate>
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem>
<!-- Demographic details of record here -->
</TabItem>
<TabItem>
<!-- Employee test info here. When user selects this tab, search db
and return tests for this employee, if any -->
<ContentControl Content="{Binding TestsVm}"
ContentTemplate="{StaticResource TestsViewTemplate}" />
</TabItem>
</TabControl>
Here are the constructor and some properties for EmployeeViewModel.cs
:
private TestsViewModel _testsVm;
private int _selectedTabIndex;
public EmployeeViewModel ()
{
// Other initialization code...
_selectedTabIndex = 0;
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(SelectedTabIndex))
{
// If tab 1 selected, the person is on the Tests tab
// Perform search and populate the TestsVM object's Tests
// by executing the RelayCommand on it
if (SelectedTabIndex.Equals(1))
{
TestsVm = new TestsViewModel
{
SelectedEmployeeId = EmployeeId
};
TestsVm.SearchTestsRelayCommand.Execute(null);
}
}
};
}
public TestsViewModel TestsVm
{
get { return _testsVm; }
set
{
if (Equals(value, _testsVm)) return;
_testsVm = value;
RaisePropertyChanged();
}
}
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value == _selectedTabIndex) return;
_selectedTabIndex = value;
RaisePropertyChanged();
}
}
Here's the ListView
in TestsView.xaml
:
<ListView ItemsSource="{Binding Tests}"
Visibility="{Binding HasTests,
Converter={helpers:BooleanToVisibilityConverter WhenTrue=Visible,
WhenFalse=Hidden}}">
<ListView.View>
<GridView>
<!-- GridView columns here -->
</GridView>
</ListView.View>
</ListView>
Here's code from TestsViewModel.cs
:
private ObservableCollection<TestViewModel> _tests;
private int _selectedEmployeeId;
private bool _hasTests;
public TestsViewModel()
{
SearchTestsRelayCommand = new RelayCommand(CallSearchTestsAsync);
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(Tests))
{
HasTests = !Tests.Count.Equals(0);
}
};
}
public RelayCommand SearchTestsRelayCommand { get; private set; }
private async void CallSearchTestsAsync()
{
await SearchTestsAsync(SelectedEmployeeId);
}
private async Task SearchTestsAsync(int employeeId)
{
ITestDataService dataService = new TestDataService();
try
{
Tests = await dataService.SearchTestsAsync(employeeId);
}
finally
{
HasTests = !Tests.Count.Equals(0);
}
}
public ObservableCollection<TestViewModel> Tests
{
get { return _tests; }
set
{
if (Equals(value, _tests)) return;
_tests = value;
RaisePropertyChanged();
}
}
public bool HasTests
{
get { return _hasTests; }
set
{
if (value == _hasTests) return;
_hasTests = value;
RaisePropertyChanged();
}
}
public int SelectedEmployeeId
{
get { return _selectedEmployeeId; }
set
{
if (value == _selectedEmployeeId) return;
_selectedEmployeeId = value;
RaisePropertyChanged();
}
}
The HasTests
property is not changing and thus not hiding the ListView
when it's empty. Note that I also tried the following for the ListView
visibility, pointing to its own HasItems
to no avail:
Visibility="{Binding HasItems,
RelativeSource={RelativeSource Self},
Converter={helpers:BooleanToVisibilityConverter WhenTrue=Visible,
WhenFalse=Hidden}}"
I've used the same BooleanToVisibilityConverter
successfully elsewhere, so it's something with my code. I'm open to your suggestions. Thank you.
Update: Here's the XAML for TestView.xaml:
<UserControl x:Class="DrugComp.Views.TestsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:DrugComp.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:viewModel="clr-namespace:DrugComp.ViewModel"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources />
<Grid Width="Auto"
Height="700"
Margin="5,7,5,5"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="32" />
<RowDefinition Height="32" />
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Style="{StaticResource Instruction}"
Text="{Binding Instructions}" />
<ListView Grid.Row="1"
Grid.ColumnSpan="2"
Width="Auto"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Top"
AlternationCount="2"
ItemContainerStyle="{DynamicResource CustomListViewItemStyle}"
ItemsSource="{Binding Tests}"
SelectedItem="{Binding SelectedTest}">
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<!-- If you want to save the place in the layout, use
Hidden instead of Collapsed -->
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.View>
<GridView>
<GridViewColumn Width="50"
DisplayMemberBinding="{Binding TestId}"
Header="Test ID" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding EmployeeId}"
Header="Employee ID" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding OrderedDate,
StringFormat='MM/dd/yyyy'}"
Header="Ordered Date" />
<GridViewColumn Width="119"
DisplayMemberBinding="{Binding ValidReasonForTest.Description}"
Header="Reason" />
<GridViewColumn Width="129"
DisplayMemberBinding="{Binding OrderedByWhom}"
Header="Ordered By" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding ScheduledDate,
StringFormat='MM/dd/yyyy'}"
Header="Scheduled Date" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>