I'm creating a UWP application that has a custom control.
I have a model that looks like this:
public enum SpanType {
Normal,
Suspend,
Resume,
}
public struct Span {
public SpanType Type;
public int StackIndex;
public string Category;
public string Name;
public UInt64 ThreadId;
public UInt64 SpanId;
public UInt64 StartTime;
public UInt64 Duration;
}
public struct ThreadSpans {
public UInt64 ThreadId;
public List<Span> Spans;
public int RowCount;
}
And the custom control:
public sealed partial class ThreadSpanControl : UserControl {
public ThreadSpanControl() {
DataContext = this;
this.InitializeComponent();
}
public List<ThreadSpans> Threads {
get { return (List<ThreadSpans>)GetValue(ThreadsProperty); }
set { SetValue(ThreadsProperty, value); }
}
public static readonly DependencyProperty ThreadsProperty =
DependencyProperty.Register("Threads", typeof(List<ThreadSpans>), typeof(ThreadSpanControl), null);
public float RowHeight {
get { return (float)GetValue(RowHeightProperty); }
set { SetValue(RowHeightProperty, value); }
}
public static readonly DependencyProperty RowHeightProperty =
DependencyProperty.Register("RowHeight", typeof(float), typeof(ThreadSpanControl), new PropertyMetadata(20.0f));
}
My main window uses it like so:
<controls:ThreadSpanControl Threads="{x:Bind ViewModel.Threads,Mode=OneWay}" />
What I'd like to do is to be able to use the RowHeight property within the ItemsControl of my Custom control. Something like:
<UserControl
x:Class="Fibofiler.Shared.Controls.ThreadSpanControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Fibofiler.Shared.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Fibofiler.Shared.Controls"
xmlns:models="using:Fibofiler.Shared.Models"
xmlns:skia="using:SkiaSharp.Views.UWP"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0" ItemsSource="{x:Bind Threads,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:ThreadSpans">
<TextBlock Text="{x:Bind ThreadId,Mode=OneWay}" Height="{x:Bind controls:Converters.CalcHeight(RowCount, this.RowHeight)}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<skia:SKXamlCanvas Grid.Column="1" PaintSurface="SKXamlCanvas_OnPaintSurface" />
</Grid>
</UserControl>
But, that's not possible. The scope of the template of the ItemsControl is only the item itself. So as far as I can tell, there's no way to access to parent's properties. Is this correct?
So, to get around this, I thought perhaps I could make a new dependency property called RowHeaders
, which just directly calculates the things I need. So then I can bind the ItemsSource
to this instead, and have all the information I need in the template.
public struct ThreadHeader {
public UInt64 ThreadId;
public double Height;
};
public sealed partial class ThreadSpanControl : UserControl {
...
public List<ThreadHeader> ThreadHeaders {
get { return (List<ThreadHeader>)GetValue(ThreadHeadersProperty); }
}
private static readonly DependencyProperty ThreadHeadersProperty =
DependencyProperty.Register("ThreadHeaders", typeof(List<ThreadHeader>), typeof(ThreadSpanControl), null);
...
}
The issue is this should be a "calculated / derived" property. That is, it directly depends on the Threads
/ RowHeight
properties. And I'm not sure how to do that with DependencyProperties. All the online information say that the "plain" Property wrapper can't have any custom logic, because during runtime, the code won't even be used.
How do I have a "calculated / derived" DependencyProperty whose value is calculated from other properties?
The other option I was thinking of was to implement INotifyPropertyChanged, and use that. However, all the places I searched said, "Never use INPC in a control. Always use use DependencyProperties. They're more efficient". Which is fine to say, but as far as I can tell, DependencyProperties are way less flexible. They can only be simple get/set with zero dependencies on others.
Thoughts? Am I just going at this the wrong way? Thus running into issues? Or is there more functionality to custom controls / DependencyProperties that I'm missing?
Additional Context
My custom context is to render a ton (20000 to 200000) of rectangles representing timespans. I initially did this with nested ItemControls, and Rectangles, but the performance was terrible due to the massive overhead of a Control per rectangle. So, instead, I'm using a SkiaSharp canvas to render all the rectangles for me. The data is all static after load, so the loss of features per rectangle is fine.
The problem I'm trying to solve here is how to line up the labels with the corresponding rows. In the picture above, you can see that they're just one after the other. From the data, I know the max number of rows per thread, and I define the row height in pixels. I just don't know the best way to set this per TextBlock for the labels.
Update 1
Following @Knoop 's suggestion, I tried naming the custom control, so I could access it from the template, but that didn't work.
<UserControl
x:Class="Fibofiler.Shared.Controls.ThreadSpanControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Fibofiler.Shared.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Fibofiler.Shared.Controls"
xmlns:models="using:Fibofiler.Shared.Models"
xmlns:skia="using:SkiaSharp.Views.UWP"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="thisControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0" ItemsSource="{x:Bind Threads,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:ThreadSpans">
<TextBlock Text="{x:Bind ThreadId,Mode=OneWay}" Height="{x:Bind controls:Converters.CalculateHeight(RowCount, thisControl.RowHeight)}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<skia:SKXamlCanvas Grid.Column="1" PaintSurface="SKXamlCanvas_OnPaintSurface" />
</Grid>
</UserControl>