WPF - 带有 OpacityMask/VisualBrush 的边框:内存泄漏

发布于 2024-11-14 11:17:51 字数 1700 浏览 3 评论 0 原文

关于我的应用程序的简要说明:

我正在开发的应用程序就是这样一个贺卡设计器。想象一下,其中有一个背景图像,以及无限数量的“层”(特别是图片),它们保留在背景上,并且可以移动、调整大小、前后移动等……

也可以将特定形状应用于这些图层,如星形、椭圆形等,制作卡片后,可以将其保存为 jpeg 文件。

问题

一切正常,但我检测到当将形状应用于图层时,会生成内存泄漏。

这里是每一层的 UserControl 的代码:

<UserControl>
.....
    <Grid x:Name="_myGrid"  >
        <Border x:Name="im_the_problem" BorderThickness="0" OpacityMask="{Binding Path=MyMask.Data, Converter={StaticResource MaskConverter}}">
        <!-- My Image... -->
        </Border>
    </Grid>
</UserControl>

其中 MaskConverter 代码如下:

public class MaskConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter,
       System.Globalization.CultureInfo culture)
    {
        String maskData = value as String;
        if (maskData == null) 
            return null;
        if (maskData == "")
            return null;
        VisualBrush vb = new VisualBrush();
        vb.Visual = XamlReader.Parse(maskData) as Visual;
        return vb;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

参数“MyMask.Data”是一个 XAML 路径 (这是我正在应用的形状),我从包含不同形状的文本文件动态加载。

所以,原则是,如果我有名为 *im_the_problem* 的边框,则不会释放内存。如果我评论*im_the_problem*(所以我只有矩形图层/没有形状的图片),一切都会像魅力一样工作,没有内存泄漏。

问题应该出在 OpacityMask + VisualBrush 上。

我做错了什么吗? 或者有一个已知的问题吗?有没有办法以不同的方式做同样的事情(将形状应用于图片..)?

谢谢。

A brief explanation about my app:

the application in which I'm working on is such a greeting cards designer. Imagine something in which there is a background image, and an indefinite number of "layers" (in particular, pictures) that stay over the background and can be moved, resized, moved front and back, etc...

It is also possibile to apply particular shapes to these layers, like a star, an ellipse, .. and after the card is made, it's possibile to save is to jpeg file.

The problem

Everything works correctly, but I detected that when a shape is applied to a layer, a memory leak is generated.

Here is the code of the UserControl of each layer:

<UserControl>
.....
    <Grid x:Name="_myGrid"  >
        <Border x:Name="im_the_problem" BorderThickness="0" OpacityMask="{Binding Path=MyMask.Data, Converter={StaticResource MaskConverter}}">
        <!-- My Image... -->
        </Border>
    </Grid>
</UserControl>

where MaskConverter code is the following:

public class MaskConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter,
       System.Globalization.CultureInfo culture)
    {
        String maskData = value as String;
        if (maskData == null) 
            return null;
        if (maskData == "")
            return null;
        VisualBrush vb = new VisualBrush();
        vb.Visual = XamlReader.Parse(maskData) as Visual;
        return vb;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

The Parameter "MyMask.Data" is a XAML Path (that is the shape that I'm applying) that I dinamically load from a textfile that contains different shapes.

So, the principle is that if I have the border named *im_the_problem*, the memory is NOT released. If I comment *im_the_problem* (so I'll just have rectangular layers/pictures without shapes) everything work like a charm, without memory leaks.

The problem should be in the OpacityMask + VisualBrush.

Am I doing something wrong?
Or is there a known problem? Is there a way to do the same (apply a shape to a picture..) in a different manner?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

栖竹 2024-11-21 11:17:51

您也许可以尝试将 MyMask.Data 绑定到实际的 Path.Data,并将 Path.Fill 设置为从图像创建的 ImageBrush?

You might be able to try binding the MyMask.Data to an actual Path.Data, and setting the Path.Fill to an ImageBrush created from the image?

留一抹残留的笑 2024-11-21 11:17:51

您需要冻结您的 VisualBrush ;)

You need to freeze your VisualBrush ;)

热情消退 2024-11-21 11:17:51

我在 DataGrid 的列模板中遇到了这个问题,其中我使用 (作为静态 -资源)到 VisualBrush(也是静态资源)中,并将其用作 矩形OpacityMask。每当重新加载 DataGrid 时,Rectangle 都不会释放对 OpacityMaskVisualBrush 引用,我使用内存分析器工具来揭示所有VisualBrush 对象正在使用大量内存。

我不明白为什么或如何发生这种情况 - 但我很高兴我并不孤单(即使大约 6.5 年后我遇到了同样的问题......)。

我的 XAML 是这样的:

<DataGrid.Resources>

    <Canvas x:Key="icon" ...>
        <Path ... />
    </Canvas>

    <VisualBrush x:Key="iconBrush" Stretch="Uniform" Visual="{StaticResource icon}" />

</DataGrid.Resources>

<DataGrid.Columns>

    <DataGridTemplateColumn>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Rectangle
                    Fill="{Binding Foreground, ElementName=myDataGrid}"
                    Width="14"
                    Height="14"
                    Margin="4"
                    Visibility="{Binding IconVisibility}"
                    OpacityMask="{StaticResource iconBrush}"
                />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>

    ...

</DataGrid.Columns>

我读到设置 IsFrozen = true (使用此技术完成:https://www.codeproject.com/Tips/72221/Freeze-brushes-directly-in-the-XAML-to-improve-you )将有助于解决画笔的内存问题,但这似乎有完全没有效果。诡异的。

我想我应该进行实验,并且我推断如果问题是泄漏 VisualBrush 那么我想知道将它作为 StaticResource 是否会扰乱对象引用,所以我改变了它到一个“拥有”的对象,就像这样:

    <DataGridTemplateColumn>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Rectangle
                    Fill="{Binding Foreground, ElementName=myDataGrid}"
                    Width="14"
                    Height="14"
                    Margin="4"
                    Visibility="{Binding IconVisibility}"
                >
                    <VisualBrush Stretch="Uniform" Visual="{StaticResource iconBrush}" />
                </Rectangle>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>

这解决了问题!我仍然不知道为什么 - 我想知道这是否是 WPF 中的一个错误?

与此相关的是,我开始意识到使用 VisualBrush 太过分了,因为我只渲染一个简单的 Path - VisualBrush 很昂贵,因为它渲染整个 WPF 视图 - 我还从其他文档了解到, Path 本身对于渲染简单形状来说不是必需的,因为它本身是一个完整的 UIElementFrameworkElement< /code> - 这是“较重”的类型。

我更改了代码以将路径存储在加载到 DrawingBrush 中的 GeometryDrawing 静态资源内的 PathGeometry 值中:

<GeometryDrawing x:Key="iconDrawing" Brush="Black" Geometry="..." /> 

<Rectangle
    Fill="{Binding Foreground, ElementName=myDataGrid}"
    Width="14"
    Height="14"
    Margin="4"
    Visibility="{Binding IconVisibility}"
    OpacityMask="{StaticResource iconBrush}"
>
    <DrawingBrush Stretch="Uniform" Drawing="{StaticResource iconDrawing}" />
</Rectangle>

这样做也使得内存使用量下降,希望性能也下降。

在您的项目中,我发现您没有将路径信息用作资源,但应用了相同的技术:将路径加载到 PathGeometry (或者更确切地说,StreamGeometry 对象)中,它甚至更快,并且适用于不可变的几何图形)并将其设置为 DrawingBrushDrawing

I had this problem in a DataGrid's column template where I was using a <Canvas><Path /></Canvas> (as a static-resource) into a VisualBrush (also a static-resource) and using that as the OpacityMask for a Rectangle. Whenever the DataGrid was reloaded the Rectangle wouldn't release VisualBrush references to the OpacityMask, I used a memory-profiler tool to reveal that all the VisualBrush objects were using the bulk of memory.

I don't understand why or how this happened - but I'm glad I'm not alone (even if I had the same problem some 6.5 years later...).

My XAML was something like this:

<DataGrid.Resources>

    <Canvas x:Key="icon" ...>
        <Path ... />
    </Canvas>

    <VisualBrush x:Key="iconBrush" Stretch="Uniform" Visual="{StaticResource icon}" />

</DataGrid.Resources>

<DataGrid.Columns>

    <DataGridTemplateColumn>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Rectangle
                    Fill="{Binding Foreground, ElementName=myDataGrid}"
                    Width="14"
                    Height="14"
                    Margin="4"
                    Visibility="{Binding IconVisibility}"
                    OpacityMask="{StaticResource iconBrush}"
                />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>

    ...

</DataGrid.Columns>

I read that setting IsFrozen = true (done using this technique: https://www.codeproject.com/Tips/72221/Freeze-brushes-directly-in-the-XAML-to-improve-you ) would help memory issues with Brushes, however this seemingly had no effect at all. Weird.

I thought I'd experiment and I reasoned that if the issue was leaking the VisualBrush then I wondered if having it as a StaticResource was messing with object-references, so I changed it to an "owned" object, like so:

    <DataGridTemplateColumn>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Rectangle
                    Fill="{Binding Foreground, ElementName=myDataGrid}"
                    Width="14"
                    Height="14"
                    Margin="4"
                    Visibility="{Binding IconVisibility}"
                >
                    <VisualBrush Stretch="Uniform" Visual="{StaticResource iconBrush}" />
                </Rectangle>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>

This fixed the issue! And I still don't know why - I wonder if it's a bug in WPF?

On a related note, I came to realise that using a VisualBrush was overkill as I'm rendering just a simple Path - VisualBrush is expensive because it renders an entire WPF view - I also learned from other documentation that Path itself isn't necessary for rendering simple shapes because itself is a complete UIElement and FrameworkElement - which are "heavier" types.

I changed my code to store the path in a PathGeometry value inside a GeometryDrawing static-resource which is loaded into a DrawingBrush:

<GeometryDrawing x:Key="iconDrawing" Brush="Black" Geometry="..." /> 

<Rectangle
    Fill="{Binding Foreground, ElementName=myDataGrid}"
    Width="14"
    Height="14"
    Margin="4"
    Visibility="{Binding IconVisibility}"
    OpacityMask="{StaticResource iconBrush}"
>
    <DrawingBrush Stretch="Uniform" Drawing="{StaticResource iconDrawing}" />
</Rectangle>

Doing this also made a dent in memory usage, and hopefully, performance.

In your project I see you're not using the path information as a resource, but the same technique applies: load your path into a PathGeometry (or rather, StreamGeometry object, which is even faster and is meant for immutable geometry) and set that as the Drawing for a DrawingBrush.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文