【问题标题】:What is the "standard" way to pass additional parameters to ItemsControl template将附加参数传递给 ItemsControl 模板的“标准”方式是什么
【发布时间】:2021-06-24 05:38:26
【问题描述】:

我正在创建一个具有自定义控件的 UWP 应用程序。

我有一个看起来像这样的模型:

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;
}

还有自定义控件:

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));
}

我的主窗口是这样使用它的:

<controls:ThreadSpanControl Threads="{x:Bind ViewModel.Threads,Mode=OneWay}" />

我想做的是能够在我的自定义控件的 ItemsControl 中使用 RowHeight 属性。比如:

<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>

但是,这是不可能的。 ItemsControl 模板的范围是项目本身。据我所知,没有办法访问父母的属性。这是正确的吗?

所以,为了解决这个问题,我想也许我可以创建一个名为RowHeaders 的新依赖属性,它直接计算我需要的东西。因此,我可以将ItemsSource 绑定到此,并在模板中包含我需要的所有信息。

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);
    
    ...
}

问题在于这个应该是一个“计算/派生”的属性。也就是说,它直接依赖于Threads / RowHeight 属性。而且我不确定如何使用 DependencyProperties 来做到这一点。网上所有的资料都说“普通”的Property wrapper不能有任何自定义逻辑,因为在运行时,代码甚至都不会被使用。

我如何拥有一个“计算/派生”的 DependencyProperty,其值是根据其他属性计算得出的?

我想到的另一个选项是实现 INotifyPropertyChanged,并使用它。但是,我搜索的所有地方都说,“永远不要在控件中使用 INPC。总是使用 use DependencyProperties。它们更有效”。可以这么说,但据我所知,DependencyProperties 不太灵活。它们只能是简单的 get/set,对他人的依赖为零。

想法?我只是走错路了吗?从而遇到问题?或者我缺少的自定义控件/DependencyProperties 是否有更多功能?

附加上下文

我的自定义上下文是渲染一吨(20000 到 200000)代表时间跨度的矩形。我最初是用嵌套的 ItemControls 和 Rectangles 做的,但由于每个矩形的 Control 开销很大,所以性能很糟糕。 因此,相反,我使用 SkiaSharp 画布为我呈现所有矩形。加载后数据都是静态的,因此每个矩形的特征损失都很好。

我在这里要解决的问题是如何将标签与相应的行对齐。在上图中,您可以看到它们只是一个接一个。从数据中,我知道每个线程的最大行数,并以像素为单位定义行高。我只是不知道为标签设置每个 TextBlock 的最佳方法。

更新 1

按照@Knoop 的建议,我尝试为自定义控件命名,这样我就可以从模板中访问它,但这不起作用。

<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>

【问题讨论】:

  • 我不清楚你到底想要达到什么目的,所以很难回答。然而这个问题“据我所知,没有办法访问父母的属性。这是正确的吗?”是不正确的。你可以x:Name你的UserControl(或者Grid,如果你想要那个)并在模板的绑定中引用它,获得对父控件属性的访问。在移动自动取款机上很难查到,但我以前用这个结构回答过问题。
  • 感谢您的回复。我想要一个项目的 ListControl,其中它们的 Height 是根据以下条件计算的:项目中的 RowCount 变量和控件中的 RowHeight 变量。我会尝试 x:Name 方法。谢谢!
  • 嗯,这似乎不起作用。查看更新
  • 你只是命名了它,但你没有在绑定中引用它。也许这可能会有所帮助:social.msdn.microsoft.com/Forums/en-US/…
  • 在使用 x:Bind(而不是 Binding)时如何引用它。因为您只能使用 x:Bind 调用函数。 stackoverflow.com/a/32851493/1757179 我不能将绑定与 ValueConverter 一起使用,因为 UWP 不支持 MultiValueConverters。

标签: c# xaml uwp dependency-properties


【解决方案1】:

您可以尝试在ItemsControlLoading 事件处理程序中获取RowHeight 属性的值,并将该值保存到全局变量中,以便以后在转换器中访问。

这里我以创建一个Converter为例:

添加一个全局变量来保存RowHeight属性的值:

public struct ThreadHeader
{
    public static float RowHeight;  //Global variable to save the value of RowHeight property
};

获取Loading事件处理程序中RowHeight属性的值:

<ItemsControl Grid.Column="0" ItemsSource="{x:Bind Threads,Mode=OneWay}" 
    Loading="ItemsControl_Loading">
    ……
</ItemsControl>

private void ItemsControl_Loading(FrameworkElement sender, object args)
{
    ThreadHeader.RowHeight = RowHeight;
}

添加转换器进行计算:

public class CalculationConverter : IValueConverter
{
    // This converts the DateTime object to the string to display.
    public object Convert(object value, Type targetType,
        object parameter, string language)
    {
        var threadSpans = (ThreadSpans)value;
        var res = threadSpans.RowCount*1.0 * ThreadHeader.RowHeight;
        return res;
    }

    // No need to implement converting back on a one-way binding 
    public object ConvertBack(object value, Type targetType,
        object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

【讨论】:

    猜你喜欢
    • 2014-03-31
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    相关资源
    最近更新 更多