WPF — Граница с непрозрачной маской / VisualBrush: утечки памяти

#memory-leaks #binding #visualbrush #opacitymask

#утечки памяти #привязка #visualbrush #непрозрачная маска

Вопрос:

Краткое объяснение моего приложения:

приложение, над которым я работаю, является таким конструктором поздравительных открыток. Представьте что-нибудь, в чем есть фоновое изображение и неопределенное количество «слоев» (в частности, картинок), которые остаются на заднем плане и могут быть перемещены, изменены в размерах, спереди и сзади и т.д…

К этим слоям также возможно применить определенные формы, такие как звезда, эллипс, .. и после того, как карта создана, ее можно сохранить в файл jpeg.

Проблема

Все работает правильно, но я обнаружил, что при применении фигуры к слою возникает утечка памяти.

Вот код пользовательского элемента управления каждого слоя:

 <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 * (так что у меня будут просто прямоугольные слои / картинки без фигур), все будет работать как по маслу, без утечек памяти.

Проблема должна быть в непрозрачной маске VisualBrush.

Я делаю что-то не так? Или есть известная проблема? Есть ли способ сделать то же самое (применить форму к изображению ..) другим способом?

Спасибо.

Ответ №1:

Возможно, вы могли бы попробовать привязать MyMask.Данные к фактическому пути.Data и настройка пути.Заливка в ImageBrush, созданный на основе изображения?

Ответ №2:

Вам нужно заморозить вашу VisualBrush 😉

Ответ №3:

У меня была эта проблема в DataGrid шаблоне столбца, где я использовал <Canvas><Path /></Canvas> (как статический ресурс) в VisualBrush (также статический ресурс) и использовал это как OpacityMask для Rectangle . Всякий раз, когда сетка данных перезагружалась, она Rectangle не выпускала VisualBrush ссылки на OpacityMask , я использовал инструмент профилирования памяти, чтобы показать, что все 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 сам по себе не является необходимым для рендеринга простых фигур, потому что сам по себе является завершенным UIElement и FrameworkElement — которые являются «более тяжелыми» типами.

Я изменил свой код, чтобы сохранить путь в PathGeometry значении внутри GeometryDrawing статического ресурса, который загружается в 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>
  

Это также повлияло на использование памяти и, надеюсь, на производительность.

В вашем проекте я вижу, что вы не используете информацию о пути в качестве ресурса, но применяется тот же метод: загрузите свой путь в PathGeometry (или, скорее, StreamGeometry объект, который еще быстрее и предназначен для неизменяемой геометрии) и установите его в качестве Drawing для DrawingBrush .