Как правильно реализовать XAML INotifyPropertyChanged для предотвращения утечки GDI - PullRequest
0 голосов
/ 30 октября 2019

У нас есть приложение, которое использует WPF для создания изображения, показывающего рабочий процесс, оно очень старое и, кажется, работало до недавнего времени, когда мы начали испытывать утечки GDI. Я не знаком с утечками WPF или GDI, но после некоторого поиска может случиться иметь дело с привязками WPF. В упомянутых статьях реализован интерфейс INotifyPropertyChanged для всех классов, которые являются обязательными в XAML. Я так и сделал, но проблема все еще остается.

Я пытался реализовать INotifyPropertyChanged для проблемных классов, упомянутых профилировщиками памяти. Также попытался установить для свойства DataContext пользовательского элемента управления значение null.

Ниже вы видите доминаторы новых объектов, созданных между двумя снимками памяти.

a busy cat

Ниже вы видите xaml и код позади доминатора.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Priox.Core.Graph.UserControls
{
    public partial class IndicatorClock : UserControl
    {
        public enum ClockTypes
        {
            None,
            FixSla,
            ResponseSla,
            FixOla,
            ResponseOla
        };

        public static readonly DependencyProperty ClockTypeProperty =
            DependencyProperty.Register("ClockType", typeof(ClockTypes),
            typeof(IndicatorClock), new FrameworkPropertyMetadata(ClockTypes.None));

        public ClockTypes ClockType
        {
            get { return (ClockTypes)GetValue(ClockTypeProperty); }
            set { SetValue(ClockTypeProperty, value); }
        }

        internal bool IsClockVisible { get; set; }

        public Visibility ClockVisibility
        {
            get
            {
                if (IsClockVisible)
                    return System.Windows.Visibility.Visible;
                return System.Windows.Visibility.Collapsed;
            }
        }

        public double ClockBorderThickness
        {
            get
            {
                return 1.5;
            }
        }

        public double ClockTimeThickness
        {
            get
            {
                return 2.0;
            }
        }

        public Brush ClockColor
        {
            get
            {
                switch (ClockType)
                {
                    case ClockTypes.FixSla:
                        return new SolidColorBrush(Colors.Cyan);
                    case ClockTypes.ResponseSla:
                        return new SolidColorBrush(Colors.DarkMagenta);
                    case ClockTypes.FixOla:
                        return new SolidColorBrush(Colors.Yellow);
                    case ClockTypes.ResponseOla:
                        return new SolidColorBrush(Colors.DarkSlateGray);
                }
                return new SolidColorBrush(Colors.Black);
            }
        }

        public IndicatorClock()
        {
            InitializeComponent();
        }
    }
}

<UserControl x:Class="Priox.Core.Graph.UserControls.IndicatorClock"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Margin="2" Visibility="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockVisibility, FallbackValue=Visible}">




            <Grid.Effect>
            <DropShadowEffect BlurRadius="3" ShadowDepth="3" Opacity="0.7"/>
        </Grid.Effect>

        <Ellipse StrokeThickness="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockBorderThickness, FallbackValue=4.0}" Stroke="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockColor, FallbackValue=Red}" />

        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="7*"/>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="7*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="7*" />
                <ColumnDefinition Width="2*" />
                <ColumnDefinition Width="7*" />
            </Grid.ColumnDefinitions>
            <Ellipse Fill="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockColor, FallbackValue=Red}" Grid.Row="1" Grid.Column="1"/>
        </Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="8*"/>
                <RowDefinition Height="10*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="6*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="6*"/>
            </Grid.ColumnDefinitions>
            <Line Grid.Row="1" Grid.Column="1" Y1="0" Y2="1" Stroke="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockColor, FallbackValue=Red}" Stretch="Fill" StrokeThickness="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockTimeThickness, FallbackValue=8.0}"/>
        </Grid>
        <Grid>
            <Grid.LayoutTransform>
                <RotateTransform CenterX="0.5" CenterY="0.5" Angle="90"/>
            </Grid.LayoutTransform>
            <Grid.RowDefinitions>
                <RowDefinition Height="4*"/>
                <RowDefinition Height="6*"/>
                <RowDefinition Height="10*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="6*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="6*"/>
            </Grid.ColumnDefinitions>
            <Line Grid.Row="1" Grid.Column="1" Y1="0" Y2="1" Stroke="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockColor, FallbackValue=Red}" Stretch="Fill" StrokeThickness="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClockTimeThickness, FallbackValue=8.0}"/>
        </Grid>
    </Grid>
</UserControl>

Как я могу это исправить, чтобы не допустить утечки GDI.

РЕДАКТИРОВАТЬ: Исправлен код в соответствии с предложением Клеменса, но пользовательский контроль все еще отображается в качестве доминирующего.

Ответы [ 2 ]

0 голосов
/ 31 октября 2019

ОК, я решил свою проблему, это не имело никакого отношения к тому, как выглядел XAML, причина, по которой я смотрел на XAML, заключалась в том, что все профилировщики памяти, которые я использовал, говорили мне, что это были проблемы. Фактическая проблема заключалась в том, что UserControl был установлен на объект, который вообще не использовался приложением. Таким образом, XAML не был собран сборщиком мусора и таким образом увеличил количество GDI.

var source = new HwndSource(new HwndSourceParameters())
{
    RootVisual = this
};
0 голосов
/ 30 октября 2019

Это похоже на архитектуру MVVM, и я рекомендую вам использовать MVVM Franmework наподобие MVVM light, это поможет избежать многих проблем и улучшить код. http://www.mvvmlight.net/ С MVVMLight вам не нужно реализовывать INotifyPropertyChanged, фреймворк делает все.

Надеюсь, это поможет.

...