如何在 ListBox 项实例之间共享资源?

Posted

技术标签:

【中文标题】如何在 ListBox 项实例之间共享资源?【英文标题】:How can you share a resource between ListBox item instances? 【发布时间】:2012-12-03 22:31:11 【问题描述】:

我们有一个自定义渲染的 ListBox,它维护一个基于其宽度的 StreamGeometry 对象的实例。然后,该控件需要将该 StreamGeometry 实例与其所有项目共享以进行渲染。

我们能想到的唯一方法是将 StreamGeometry 实例放在 ListBox 的 ViewModel 中,然后在单独的 DataTemplates 中绑定到它,考虑到这是一个仅限视图的东西,因此不应该在完全是 ViewModel。

注意:我们也可以通过 ListBox 上的附加属性(或 ListBox 的子类)来存储它,但我们仍然需要绑定一个仅查看的东西,这对我来说似乎是错误的。

有什么想法吗?

【问题讨论】:

Window.Resources 呢? 不,因为它的 ListBox 实例是特定的并且可以有很多。另外,它是由 ListBox 的属性决定的。我的实际问题更多是关于通过数据模板对项目本身的实际共享。我的意思是这甚至是正确的方法吗?我认为绑定可能很慢。 资源字典中的一个对象(例如,一个 PathGeometry,如果这就是您所说的 GraphicsPath 的意思)可以由任意数量的 ListBoxItem 共享。 Clemens,正如我所说,它是 ListBox 的每个实例,这意味着每个 ListBox 必须维护自己的实例。问题更多的是从 ListBox(或它的 ViewModel)获取 StreamGeometry 实例到单个项目模板实例的最有效方法。 StreamGeometry 属性的绑定似乎会很慢,但我可能是错的。 那么当每个 ListBoxItem 都有自己的实例时,“共享”到底是什么意思?也许您提供一些示例 XAML? 【参考方案1】:

您可以使 StreamGeometry 成为自定义列表视图上的依赖属性,然后通过 Binding MyGeometry, RelativeSource=RelativeSource AncestorType=ListView 引用它。

这样,不涉及 ViewModel。

Xaml:

<Window x:Class="WpfApplication1.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:WpfApplication1"
                xmlns:s="clr-namespace:System;assembly=mscorlib"
                Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <!-- default lsitviewitem style except for added path -->
        <Style TargetType="x:Type ListViewItem">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="HorizontalContentAlignment" Value="Binding HorizontalContentAlignment, RelativeSource=RelativeSource AncestorType=x:Type ItemsControl"/>
            <Setter Property="VerticalContentAlignment" Value="Binding VerticalContentAlignment, RelativeSource=RelativeSource AncestorType=x:Type ItemsControl"/>
            <Setter Property="Padding" Value="2,0,0,0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="x:Type ListViewItem">
                        <Border x:Name="Bd" BorderBrush="TemplateBinding BorderBrush" BorderThickness="TemplateBinding BorderThickness" Background="TemplateBinding Background" Padding="TemplateBinding Padding" SnapsToDevicePixels="true">
                            <StackPanel Orientation="Horizontal">
                                <ContentPresenter HorizontalAlignment="TemplateBinding HorizontalContentAlignment" SnapsToDevicePixels="TemplateBinding SnapsToDevicePixels" VerticalAlignment="TemplateBinding VerticalContentAlignment"/>
                                <!-- added path-->
                                <Path Stretch="Uniform" Stroke="DarkBlue" Fill="DarkOrchid" Data="Binding MyGeometry, RelativeSource=RelativeSource AncestorType=ListView" />
                            </StackPanel>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="true">
                                <Setter Property="Background" TargetName="Bd" Value="DynamicResource x:Static SystemColors.HighlightBrushKey"/>
                                <Setter Property="Foreground" Value="DynamicResource x:Static SystemColors.HighlightTextBrushKey"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsSelected" Value="true"/>
                                    <Condition Property="Selector.IsSelectionActive" Value="false"/>
                                </MultiTrigger.Conditions>
                                <Setter Property="Background" TargetName="Bd" Value="DynamicResource x:Static SystemColors.InactiveSelectionHighlightBrushKey"/>
                                <Setter Property="Foreground" Value="DynamicResource x:Static SystemColors.InactiveSelectionHighlightTextBrushKey"/>
                            </MultiTrigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="DynamicResource x:Static SystemColors.GrayTextBrushKey"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <Grid >
        <local:CustomListView Margin="20" >
            <local:CustomListView.Items>
                <ListViewItem Content="ListViewItem1" />
                <ListViewItem Content="ListViewItem2" />
                <ListViewItem Content="ListViewItem3" />
            </local:CustomListView.Items>
        </local:CustomListView>
    </Grid>
</Window>

自定义列表视图:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WpfApplication1

    public class CustomListView : ListView
    
        public StreamGeometry MyGeometry  get  return (StreamGeometry)GetValue(MyGeometryProperty);  set  SetValue(MyGeometryProperty, value);  
        public static readonly DependencyProperty MyGeometryProperty = DependencyProperty.Register("MyGeometry", typeof(StreamGeometry), typeof(CustomListView), new PropertyMetadata(null));

        protected override void OnRender(DrawingContext drawingContext)
        
            StreamGeometry geometry = new StreamGeometry(); // directly opening MyGeometry results in "must have isfrozen set to false to modify" error
            using (StreamGeometryContext context = geometry.Open()) 
            
                Point p1 = new Point(this.ActualWidth * (2d / 5d), 0);
                Point p2 = new Point(this.ActualWidth / 2d, -10);
                Point p3 = new Point(this.ActualWidth * (3d / 5d), 0);

                context.BeginFigure(p1, true, true);

                List<Point> points = new List<Point>()  p2, p3 ;
                context.PolyLineTo(points, true, true);
            

            drawingContext.DrawGeometry(Brushes.DarkOrchid, new Pen(Brushes.DarkBlue, 1), geometry);

            this.MyGeometry = geometry;

            base.OnRender(drawingContext);
        
    

【讨论】:

这与我在问题中提到的相似;唯一的区别是我说的是使用附加属性,而您使用的是子类。此外,我们不想使用这样的绑定,因为我们在使用路径的 ListBoxItems 中进行自定义渲染,而您使用可视化树,我相信*会使 UI 混乱(我们的路径非常复杂。)(我打了星号是因为在幕后,渲染实际上可能是在构建视觉树本身,这意味着我的陈述只是部分正确。我需要在那里进行更多的研究。) 看来我无法获得您的目标。我试图理解:当你说你自定义渲染你的列表框时,这并不需要一个子类,所以你没有覆盖 OnRender 方法?什么是“混乱”的 UI,反应迟钝?我认为我的方法有一个共享路径实例。如果您想自定义呈现它而不是直接将其放入可视化树中,则可以将其绑定到 CustomListViewItem 的属性。至于绑定,我绝对不明白你的担心。这是绑定到另一个视觉对象的 dp,与 Data/ViewModel 无关! 我们正在通过ItemContainerStyle 应用的 ListBoxItem 的子类中进行自定义渲染。然而,我们希望 列表框的单个实例 的每个 ListBoxItem 共享一个路径,因此 ListBox 自然必须保存该路径的实例。我只是想知道是否有一个好的方法可以做到这一点。您的方式将起作用,与附加属性相同。我只是讨厌使用绑定来获取它的想法,更不用说使用RelativeSource,但我不知道ListBoxItem 获取它的任何其他方式。 (话又说回来,我认为它确实对 ItemsControl 有一些参考) 好吧,您可以通过(this.Parent as CustomListView).MyGeometry 访问 CustomListViewItem 中的 StreamGeometry,但在我看来,您最终会尝试以这种方式制造您自己的绑定实现。 实际上,从我发的另一篇文章 (***.com/questions/14118414/…) 中,容器的父级(即 ListBoxItem、ComboBoxItem 等)返回 null,而不是 ItemsControl 本身,所以这也不起作用。我同意它应该是非常简单的(而且根本不会是一种约束力!效率更高!),但 MS 的权力显然不同意。如果你问我就疯了。

以上是关于如何在 ListBox 项实例之间共享资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何在同一个 monorepo 中的 Python 项目之间共享开发依赖项?

如何使用私有IP在两个项目之间共享Google Cloud SQL实例?

如何在 2 个进程之间共享 COM 对象?

在项目之间共享类,同时绕过 stdafx.h 依赖项

在 monorepo 中的多个应用程序之间共享组件时如何处理共享依赖项

在 OS X 上的 OpenGL 上下文之间共享数据(不同的版本/配置文件)