I'm making a user control for a data grid. This data grid has some old and new entries. It is required that the new entries are shown at the top of the screen yet the order of data has to be chronological. So I am pushing the whole grid upwards by adding empty rows. For example if I have 8 old entries and 2 new entries, the 8 old entries have to be "hidden" above the first of the two new entries.
To calculate the empty rows, I want to divide the actual height of the user control by the row height. I have found a way to get the actual height through a dependency property (see code below). I need it available at the start, so I implemented a trigger to give me acces to the load event. Yet at this time the user control still has a size of 0. So is there another event that would allow me to calculate this on the correct timing?
A last note, I am using Galasoft MVVMLight and a matching mvvm-pattern. Also the dependency property does give the right height once you change the height of the window after first initialization.
The view
<DataGrid x:Class="Kwa.Presentation.Views.AlarmList.AlarmList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Kwa.Presentation.Views.AlarmList"
xmlns:behaviors="clr-namespace:Kwa.Presentation.Behaviors"
xmlns:Trans="clr-namespace:Kwa.Presentation.Resources"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750"
ItemsSource="{Binding Alarms}"
SelectedItem="{Binding SelectedAlarm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionChanged = "AlarmFramework_SelectionChanged"
IsSynchronizedWithCurrentItem="True"
CanUserResizeColumns="True" IsReadOnly="True" CanUserReorderColumns="False" CanUserSortColumns="False" SelectionMode="Single" CanUserAddRows="False"
Background="White" RowHeaderWidth="0" AutoGenerateColumns="False" GridLinesVisibility="None" RowHeight="20" FrozenColumnCount = "1"
ScrollViewer.CanContentScroll="False" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
x:Name="AlarmFramework"
behaviors:ElementActualSizeBehavior.ActualHeight="{Binding ListHeight}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding AlarmListLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTemplateColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderTime}" Width="auto" HeaderStyle="{StaticResource WithButt}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<components:FlagControl VerticalAlignment="Center" Height="15" Width="15" FlagColor="{Binding Severity, Converter={StaticResource AlarmSeverityToColorConverter}}"
Visibility="{Binding AckStatus, Converter={StaticResource InvertedBoolToVisibilityConverter}, Mode=TwoWay}"/>
<TextBlock Text="{Binding DateTimeString}" Padding="10,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderSeverity}" Binding="{Binding SeverityString}" Width="auto" HeaderStyle="{StaticResource WithoutButt}"/>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderDescription}" Binding="{Binding Description}" d:Width="400" Width="*" HeaderStyle="{StaticResource WithoutButt}"/>
</DataGrid.Columns>
</DataGrid>
Dependency property to get actual height
public class ElementActualSizeBehavior
{
public static double GetActualWidth(DependencyObject obj)
{
return (double)obj.GetValue(ActualWidthProperty);
}
public static void SetActualWidth(DependencyObject obj, double value)
{
obj.SetValue(ActualWidthProperty, value);
}
public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
public static readonly DependencyProperty ActualWidthProperty = DependencyProperty.RegisterAttached("ActualWidth", typeof(double), typeof(ElementActualSizeBehavior), new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(ActualWidthChanged)) { BindsTwoWayByDefault = true });
public static readonly DependencyProperty ActualHeightProperty = DependencyProperty.RegisterAttached("ActualHeight", typeof(double), typeof(ElementActualSizeBehavior), new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(ActualHeightChanged)) { BindsTwoWayByDefault = true });
private static void ActualHeightChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Control w = source as Control;
if (w != null)
{
w.SizeChanged += (se, ev) =>
{
SetActualHeight((DependencyObject)se, ev.NewSize.Height);
};
}
}
private static void ActualWidthChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
Control w = source as Control;
if (w != null)
{
w.SizeChanged += (se, ev) =>
{
SetActualWidth((DependencyObject)se, ev.NewSize.Width);
};
}
}
}
The view model
public class AlarmListViewModel : MainViewModelBase
{
#region Properties
private double _listHeight;
public double ListHeight
{
get { return _listHeight; }
set
{
if (Double.IsNaN(value))
{
//Debug.WriteLine("nan");
return;
}
_listHeight = value;
//Debug.WriteLine(value);
RaisePropertyChanged(() => ListHeight);
}
}
private ObservableCollection<AlarmEntryViewModel> _alarms;
public ObservableCollection<AlarmEntryViewModel> Alarms
{
get { return _alarms; }
set { Set(() => Alarms, ref _alarms, value); }
}
private AlarmEntryViewModel _selectedAlarm;
public AlarmEntryViewModel SelectedAlarm
{
get { return _selectedAlarm; }
set { Set(() => SelectedAlarm, ref _selectedAlarm, value); }
}
private int _acknowledgeAllowed;
public int AcknowledgeAllowed
{
get { return _acknowledgeAllowed; }
set { Set(() => AcknowledgeAllowed, ref _acknowledgeAllowed, value); }
}
private int _acknowledgableAlarms;
public int AcknowledgableAlarms
{
get { return _acknowledgableAlarms; }
set { Set(() => AcknowledgableAlarms, ref _acknowledgableAlarms, value); }
}
private int _rowHeight;
public int RowHeight
{
get { return _rowHeight; }
set { Set(() => RowHeight, ref _rowHeight, value); }
}
private readonly IActionCommand _acknowledgeCommand;
public IActionCommand AcknowledgeCommand
{
get { return _acknowledgeCommand; }
}
private readonly IActionCommand _alarmListLoadedCommand;
public IActionCommand AlarmListLoadedCommand
{
get { return _alarmListLoadedCommand; }
}
public int MaxAcknowledgedAlarm;
public int AlarmToSendIndex { get; set; }
#endregion
#region Constructor
public AlarmListViewModel()
{
//Lock collection to stop inconsistent item source exception
Alarms = new ObservableCollection<AlarmEntryViewModel>();
BindingOperations.EnableCollectionSynchronization(Alarms, _AlarmsLock);
//Add command
_acknowledgeCommand = new ActionCommand<AlarmEntryViewModel>(p => Acknowledge(p));
_alarmListLoadedCommand = new ActionCommand<AlarmListViewModel>(p => Startup());
}
#endregion
#region Private Methods
private void Startup()
{
var rowsInView = (int)Math.Floor(ListHeight / RowHeight);
var rowsInAlarms = Alarms.Count;
var rowsNeeded = rowsInView - rowsInAlarms;
}