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