Reputation: 3
I am trying to have an items visibility change based on the selected ListViewItem. Basically every column in the ListView has two items on a grid, a label and a control (Combobox, DatePicker, Textbox, etc). If the ListViewItem is selected then I want all of the controls in that row to be visible, else the label should be visible. This is on a UserControl, not a Window, if that makes any difference.
Here's my view model
public class DailyServiceLogsViewModel
{
public int DailyServiceLogID { get; set; }
public int EmployeePositionID { get; set; }
public PositionType SelectedEmployeePosition { get; set; }
public List<PositionType> EmployeePositionList { get; set; }
public List<EmployeeSelectionListViewModel> EmployeeList { get; set; }
public EmployeeSelectionListViewModel SelectedEmployee { get; set; }
public string EmployeeName { get; set; }
public string PositionDescription { get; set; }
public DateTime? Date { get; set; }
public string WorkArea { get; set; }
public bool SelectedLog { get; set; }
}
Code behind
private DBContext _dbContext= new DBContext();
public ObservableCollection<DailyServiceLogsViewModel> DailyServiceLogs { get; set; }
public void OnLoad()
{
_dbContext= new DBContext();
List<EmployeeSelectionListViewModel> employeeList = _dbContext.Employees.Where(emp => emp.Active).Select(employee => new EmployeeSelectionListViewModel { EmployeeID = employee.EmployeeID, EmployeeName = employee.FirstName + " " + employee.LastName }).ToList();
DailyServiceLogs = new ObservableCollection<DailyServiceLogsViewModel>();
foreach (var serviceLog in _dbContext.DailyServiceLogs.Where(d => d.PayPeriodID == CurrentPayPeriod.PayPeriodID).OrderBy(d => d.EmployeePosition.Employee.LastName).ThenBy(d => d.EmployeePosition.Employee.FirstName))
{
DailyServiceLogs.Add(new DailyServiceLogsViewModel
{
DailyServiceLogID = serviceLog.DailyServiceLogID,
EmployeePositionID = serviceLog.EmployeePositionID,
SelectedEmployeePosition = serviceLog.EmployeePosition.PositionType,
EmployeeName = serviceLog.EmployeePosition.Employee.FirstName + " " + serviceLog.EmployeePosition.Employee.LastName,
Date = serviceLog.Date,
PositionDescription = serviceLog.EmployeePosition.PositionType.Description,
WorkArea = serviceLog.Workarea,
EmployeeList = employeeList,
SelectedEmployee = new EmployeeSelectionListViewModel { EmployeeID = serviceLog.EmployeePosition.EmployeeID, EmployeeName = serviceLog.EmployeePosition.Employee.FirstName + " " + serviceLog.EmployeePosition.Employee.LastName },
EmployeePositionList = _dbContext.PositionTypes.Where(pt => pt.Active && _payrollContext.EmployeePositions.Any(ep => ep.EmployeeID == serviceLog.EmployeePosition.EmployeeID && ep.PositionTypeID == pt.PositionTypeID)).ToList(),
SelectedLog = false
});
}
ListViewTest.DataContext = this;
ListViewTest.ItemsSource = DailyServiceLogs;
}
private void DailyServiceLog_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!(sender is ListView senderListView)) return;
if (senderListView.SelectedItem == null) return;
if (senderListView.SelectedItem.GetType() == typeof(DailyServiceLogsViewModel))
{
foreach (var log in DailyServiceLogs)
{
log.SelectedLog = log.DailyServiceLogID == ((DailyServiceLogsViewModel) senderListView.SelectedItem).DailyServiceLogID;
}
}
}
I've tried using DataTriggers, but I'm not too familiar with them
<ListView Name="ListViewTest" Grid.Row="1" ItemsSource="{Binding}" SelectionChanged="DailyServiceLog_OnSelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn x:Name="clmServiceEmployeeName" Header="Employee" Width="155">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="gdEmployee" Width="{Binding ElementName=clmServiceEmployeeName, Path=Width}" Tag="{Binding DailyServiceLogID}">
<Label Content="{Binding EmployeeName}" >
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.SelectedLog}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<ComboBox Tag="{Binding ElementName=gdEmployee, Path=Tag}" ItemsSource="{Binding EmployeeList}" SelectedValue="{Binding SelectedEmployee.EmployeeID}" DisplayMemberPath="EmployeeName" SelectedValuePath="EmployeeID" FlowDirection="LeftToRight" Margin="15,5" HorizontalAlignment="Stretch" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.SelectedLog}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
</GridView>
</ListView.View>
</ListView>
I have also tried with a converter
<ListView Name="ListViewTest" Grid.Row="1" ItemsSource="{Binding}" SelectionChanged="DailyServiceLog_OnSelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn x:Name="clmServiceEmployeeName" Header="Employee" Width="155">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="gdEmployee" Width="{Binding ElementName=clmServiceEmployeeName, Path=Width}" Tag="{Binding DailyServiceLogID}">
<Grid.Resources>
<dataconverter:BoolVisibilityConverter x:Key="BoolVisibilityConverter"/>
<dataconverter:InvertedBoolVisibilityConverter x:Key="InvertedBoolVisibilityConverter"/>
</Grid.Resources>
<Label Content="{Binding EmployeeName}" Visibility="{Binding ElementName=PayrollControl, Path=SelectedServiceLog, Converter={StaticResource InvertedBoolVisibilityConverter}}"/>
<ComboBox Tag="{Binding ElementName=gdEmployee, Path=Tag}" ItemsSource="{Binding EmployeeList}" SelectedValue="{Binding SelectedEmployee.EmployeeID}" Visibility="{Binding SelectedServiceLog, Converter={StaticResource BoolVisibilityConverter}}" DisplayMemberPath="EmployeeName" SelectedValuePath="EmployeeID" FlowDirection="LeftToRight" Margin="15,5" HorizontalAlignment="Stretch" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Any idea how to get this to function, or is there a better way to go about what I am trying to achieve? Any help would be greatly appreciated.
Upvotes: 0
Views: 175
Reputation: 37057
The proper way to keep SelectedLog
updated would be to make an ItemContainerStyle
for the ListView, and bind it there. That's the correct solution. You'll need to make your item viewmodel an actual viewmodel that implements INotifyPropertyChanged
. As it is, changing that property will never affect anything in the UI, because the UI isn't being notified of changes. That solution follows.
But if the viewmodel doesn't particularly care whether it's selected we could remove your SelectionChanged handler, remove SelectedLog
from the item class, and bind directly to the actual IsSelected property of the ListViewItem in our triggers. Note that you set the ComboBox visible when the item was selected, but you never hid it when the item wasn't selected. I've fixed that. One control is hidden when we're selected, the other is hidden when we're not selected.
<Label Content="{Binding EmployeeName}" >
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<ComboBox Tag="{Binding ElementName=gdEmployee, Path=Tag}" ItemsSource="{Binding EmployeeList}" SelectedValue="{Binding SelectedEmployee.EmployeeID}" DisplayMemberPath="EmployeeName" SelectedValuePath="EmployeeID" FlowDirection="LeftToRight" Margin="15,5" HorizontalAlignment="Stretch" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You should learn how to create and bind viewmodel properties, so here's that solution. First, we need to make the "viewmodel" an actual viewmodel:
public class DailyServiceLogsViewModel : ViewModelBase
{
public int DailyServiceLogID { get; set; }
public int EmployeePositionID { get; set; }
public PositionType SelectedEmployeePosition { get; set; }
public List<PositionType> EmployeePositionList { get; set; }
public List<EmployeeSelectionListViewModel> EmployeeList { get; set; }
public EmployeeSelectionListViewModel SelectedEmployee { get; set; }
public string EmployeeName { get; set; }
public string PositionDescription { get; set; }
public DateTime? Date { get; set; }
public string WorkArea { get; set; }
// Only properties like this will notify the UI when they update.
private bool _isSelectedLog = false;
public bool IsSelectedLog
{
get => _isSelectedLog;
set => SetProperty(ref _isSelectedLog, value);
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
public void SetProperty<T>(ref T field, T value, [CallerMemberName] string propName = null)
{
if (!Object.Equals(field, value))
{
field = value;
OnPropertyChanged(propName);
}
}
}
Second, add an ItemContainerStyle that sets IsSelectedLog
when when the item is selected. You can remove the SelectionChanged handler.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelectedLog}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn x:Name="clmServiceEmployeeName" Header="Employee" Width="155">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Tag="{Binding DailyServiceLogID}">
<Label Content="{Binding EmployeeName}" >
<Label.Style>
<Style TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedLog}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<ComboBox Tag="{Binding ElementName=gdEmployee, Path=Tag}" ItemsSource="{Binding EmployeeList}" SelectedValue="{Binding SelectedEmployee.EmployeeID}" DisplayMemberPath="EmployeeName" SelectedValuePath="EmployeeID" FlowDirection="LeftToRight" Margin="15,5" HorizontalAlignment="Stretch" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedLog}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Upvotes: 0