Wpf AvalonControls RangeSlaider内存泄漏(DependencyPropertyDescriptor…AddValueChanged)
我一直在使用AvalonControls RangeSloider,在数据模板中使用时发现了相当多的内存泄漏。我对此进行了研究,得出以下结论:Wpf AvalonControls RangeSlaider内存泄漏(DependencyPropertyDescriptor…AddValueChanged),wpf,memory,memory-leaks,Wpf,Memory,Memory Leaks,我一直在使用AvalonControls RangeSloider,在数据模板中使用时发现了相当多的内存泄漏。我对此进行了研究,得出以下结论: DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(RangeSlider)). RemoveValueChanged(this, delegate { ReCalculateWidths(); }); 这就是说,我不明白RangeS
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(RangeSlider)).
RemoveValueChanged(this, delegate { ReCalculateWidths(); });
这就是说,我不明白RangeSlaider类在哪里适用于RemoveValue更改,我认为这将解决此泄漏。应该注意的是,我在选项卡上使用带有DataTemplate/ItemsTemplate的控件
下面是课程(我为冗长的片段道歉)。非常感谢您的指导
{
#region Data members
bool internalUpdate = false;
const double RepeatButtonMoveRatio = 0.1;//used to move the selection by x ratio when click the repeat buttons
const double DefaultSplittersThumbWidth = 10;
Thumb centerThumb; //the center thumb to move the range around
Thumb leftThumb;//the left thumb that is used to expand the range selected
Thumb rightThumb;//the right thumb that is used to expand the range selected
RepeatButton leftButton;//the left side of the control (movable left part)
RepeatButton rightButton;//the right side of the control (movable right part)
StackPanel visualElementsContainer;//stackpanel to store the visual elements for this control
#endregion
#region properties and events
/// <summary>
/// The min value for the range of the range slider
/// </summary>
public long RangeStart
{
get { return (long)GetValue(RangeStartProperty); }
set { SetValue(RangeStartProperty, value); }
}
/// <summary>
/// The min value for the range of the range slider
/// </summary>
public static readonly DependencyProperty RangeStartProperty =
DependencyProperty.Register("RangeStart", typeof(long), typeof(RangeSlider),
new UIPropertyMetadata((long)0,
delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RangeSlider slider = (RangeSlider)sender;
if (!slider.internalUpdate)//check if the property is set internally
{
slider.ReCalculateRanges();
slider.ReCalculateWidths();
}
}));
/// <summary>
/// The max value for the range of the range slider
/// </summary>
public long RangeStop
{
get { return (long)GetValue(RangeStopProperty); }
set { SetValue(RangeStopProperty, value); }
}
/// <summary>
/// The max value for the range of the range slider
/// </summary>
public static readonly DependencyProperty RangeStopProperty =
DependencyProperty.Register("RangeStop", typeof(long), typeof(RangeSlider),
new UIPropertyMetadata((long)1,
delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RangeSlider slider = (RangeSlider)sender;
if (!slider.internalUpdate)//check if the property is set internally
{
slider.ReCalculateRanges();
slider.ReCalculateWidths();
}
}));
/// <summary>
/// The min value of the selected range of the range slider
/// </summary>
public long RangeStartSelected
{
get { return (long)GetValue(RangeStartSelectedProperty); }
set { SetValue(RangeStartSelectedProperty, value); }
}
/// <summary>
/// The min value of the selected range of the range slider
/// </summary>
public static readonly DependencyProperty RangeStartSelectedProperty =
DependencyProperty.Register("RangeStartSelected", typeof(long), typeof(RangeSlider),
new UIPropertyMetadata((long)0,
delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RangeSlider slider = (RangeSlider)sender;
if (!slider.internalUpdate)//check if the property is set internally
{
slider.ReCalculateWidths();
slider.OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(slider));
}
}));
/// <summary>
/// The max value of the selected range of the range slider
/// </summary>
public long RangeStopSelected
{
get { return (long)GetValue(RangeStopSelectedProperty); }
set { SetValue(RangeStopSelectedProperty, value); }
}
/// <summary>
/// The max value of the selected range of the range slider
/// </summary>
public static readonly DependencyProperty RangeStopSelectedProperty =
DependencyProperty.Register("RangeStopSelected", typeof(long), typeof(RangeSlider),
new UIPropertyMetadata((long)1,
delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
RangeSlider slider = (RangeSlider)sender;
if (!slider.internalUpdate)//check if the property is set internally
{
slider.ReCalculateWidths();
slider.OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(slider));
}
}));
/// <summary>
/// The min range value that you can have for the range slider
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when MinRange is set less than 0</exception>
public long MinRange
{
get { return (long)GetValue(MinRangeProperty); }
set { SetValue(MinRangeProperty, value); }
}
/// <summary>
/// The min range value that you can have for the range slider
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when MinRange is set less than 0</exception>
public static readonly DependencyProperty MinRangeProperty =
DependencyProperty.Register("MinRange", typeof(long), typeof(RangeSlider),
new UIPropertyMetadata((long)0,
delegate(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((long)e.NewValue < 0)
throw new ArgumentOutOfRangeException("value", "value for MinRange cannot be less than 0");
RangeSlider slider = (RangeSlider)sender;
if (!slider.internalUpdate)//check if the property is set internally
{
slider.internalUpdate = true;//set flag to signal that the properties are being set by the object itself
slider.RangeStopSelected = Math.Max(slider.RangeStopSelected, slider.RangeStartSelected + (long)e.NewValue);
slider.RangeStop = Math.Max(slider.RangeStop, slider.RangeStopSelected);
slider.internalUpdate = false;//set flag to signal that the properties are being set by the object itself
slider.ReCalculateRanges();
slider.ReCalculateWidths();
}
}));
/// <summary>
/// Event raised whenever the selected range is changed
/// </summary>
public static readonly RoutedEvent RangeSelectionChangedEvent =
EventManager.RegisterRoutedEvent("RangeSelectionChanged",
RoutingStrategy.Bubble, typeof(RangeSelectionChangedEventHandler), typeof(RangeSlider));
/// <summary>
/// Event raised whenever the selected range is changed
/// </summary>
public event RangeSelectionChangedEventHandler RangeSelectionChanged
{
add { AddHandler(RangeSelectionChangedEvent, value); }
remove { RemoveHandler(RangeSelectionChangedEvent, value); }
}
#endregion
#region Commands
/// <summary>
/// Command to move back the selection
/// </summary>
public static RoutedUICommand MoveBack =
new RoutedUICommand("MoveBack", "MoveBack", typeof(RangeSlider),
new InputGestureCollection(new InputGesture[] {
new KeyGesture(Key.B, ModifierKeys.Control)
}));
/// <summary>
/// Command to move forward the selection
/// </summary>
public static RoutedUICommand MoveForward =
new RoutedUICommand("MoveForward", "MoveForward", typeof(RangeSlider),
new InputGestureCollection(new InputGesture[] {
new KeyGesture(Key.F, ModifierKeys.Control)
}));
/// <summary>
/// Command to move all forward the selection
/// </summary>
public static RoutedUICommand MoveAllForward =
new RoutedUICommand("MoveAllForward", "MoveAllForward", typeof(RangeSlider),
new InputGestureCollection(new InputGesture[] {
new KeyGesture(Key.F, ModifierKeys.Alt)
}));
/// <summary>
/// Command to move all back the selection
/// </summary>
public static RoutedUICommand MoveAllBack =
new RoutedUICommand("MoveAllBack", "MoveAllBack", typeof(RangeSlider),
new InputGestureCollection(new InputGesture[] {
new KeyGesture(Key.B, ModifierKeys.Alt)
}));
#endregion
/// <summary>
/// Default constructor
/// </summary>
public RangeSlider()
{
CommandBindings.Add(new CommandBinding(MoveBack, MoveBackHandler));
CommandBindings.Add(new CommandBinding(MoveForward, MoveForwardHandler));
CommandBindings.Add(new CommandBinding(MoveAllForward, MoveAllForwardHandler));
CommandBindings.Add(new CommandBinding(MoveAllBack, MoveAllBackHandler));
//hook to the size change event of the range slider
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(RangeSlider)).
AddValueChanged(this, delegate { ReCalculateWidths(); });
}
/// <summary>
/// Static constructor
/// </summary>
static RangeSlider()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(RangeSlider), new FrameworkPropertyMetadata(typeof(RangeSlider))
);
}
#region Command handlers
void MoveAllBackHandler(object sender, ExecutedRoutedEventArgs e)
{
ResetSelection(true);
}
void MoveAllForwardHandler(object sender, ExecutedRoutedEventArgs e)
{
ResetSelection(false);
}
void MoveBackHandler(object sender, ExecutedRoutedEventArgs e)
{
MoveSelection(true);
}
void MoveForwardHandler(object sender, ExecutedRoutedEventArgs e)
{
MoveSelection(false);
}
#endregion
#region event handlers for visual elements to drag the range
//drag thumb from the right splitter
private void RightThumbDragDelta(object sender, DragDeltaEventArgs e)
{
MoveThumb(centerThumb, rightButton, e.HorizontalChange);
ReCalculateRangeSelected(false, true);
}
//drag thumb from the left splitter
private void LeftThumbDragDelta(object sender, DragDeltaEventArgs e)
{
MoveThumb(leftButton, centerThumb, e.HorizontalChange);
ReCalculateRangeSelected(true, false);
}
//left repeat button clicked
private void LeftButtonClick(object sender, RoutedEventArgs e)
{
MoveSelection(true);
}
//right repeat button clicked
private void RightButtonClick(object sender, RoutedEventArgs e)
{
MoveSelection(false);
}
//drag thumb from the middle
private void CenterThumbDragDelta(object sender, DragDeltaEventArgs e)
{
MoveThumb(leftButton, rightButton, e.HorizontalChange);
ReCalculateRangeSelected(true, true);
}
#endregion
#region logic to resize range
//resizes the left column and the right column
private static void MoveThumb(FrameworkElement x, FrameworkElement y, double horizonalChange)
{
double change = 0;
if (horizonalChange < 0) //slider went left
change = GetChangeKeepPositive(x.Width, horizonalChange);
else if (horizonalChange > 0) //slider went right if(horizontal change == 0 do nothing)
change = -GetChangeKeepPositive(y.Width, -horizonalChange);
x.Width += change;
y.Width -= change;
}
//ensures that the new value (newValue param) is a valid value. returns false if not
private static double GetChangeKeepPositive(double width, double increment)
{
return Math.Max(width + increment, 0) - width;
}
#endregion
#region logic to calculate the range
long movableRange = 0;
double movableWidth = 0;
//recalculates the movableRange. called from the RangeStop setter, RangeStart setter and MinRange setter
private void ReCalculateRanges()
{
movableRange = RangeStop - RangeStart - MinRange;
}
//recalculates the movableWidth. called whenever the width of the control changes
private void ReCalculateWidths()
{
if (leftButton != null && rightButton != null && centerThumb != null)
{
movableWidth = Math.Max(ActualWidth - rightThumb.ActualWidth - leftThumb.ActualWidth - centerThumb.MinWidth, 1);
leftButton.Width = Math.Max(movableWidth * (RangeStartSelected - RangeStart) / movableRange, 0);
rightButton.Width = Math.Max(movableWidth * (RangeStop - RangeStopSelected) / movableRange, 0);
centerThumb.Width = Math.Max(ActualWidth - leftButton.Width - rightButton.Width - rightThumb.ActualWidth - leftThumb.ActualWidth, 0);
}
}
//recalculates the rangeStartSelected called when the left thumb is moved and when the middle thumb is moved
//recalculates the rangeStopSelected called when the right thumb is moved and when the middle thumb is moved
private void ReCalculateRangeSelected(bool reCalculateStart, bool reCalculateStop)
{
internalUpdate = true;//set flag to signal that the properties are being set by the object itself
if (reCalculateStart)
{
// Make sure to get exactly rangestart if thumb is at the start
if (leftButton.Width == 0.0)
RangeStartSelected = RangeStart;
else
RangeStartSelected =
Math.Max(RangeStart, (long)(RangeStart + movableRange * leftButton.Width / movableWidth));
}
if (reCalculateStop)
{
// Make sure to get exactly rangestop if thumb is at the end
if (rightButton.Width == 0.0)
RangeStopSelected = RangeStop;
else
RangeStopSelected =
Math.Min(RangeStop, (long)(RangeStop - movableRange * rightButton.Width / movableWidth));
}
internalUpdate = false;//set flag to signal that the properties are being set by the object itself
if (reCalculateStart || reCalculateStop)
//raise the RangeSelectionChanged event
OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(this));
}
/// <summary>
/// moves the current selection with x value
/// </summary>
/// <param name="isLeft">True if you want to move to the left</param>
public void MoveSelection(bool isLeft)
{
double widthChange = RepeatButtonMoveRatio * (RangeStopSelected - RangeStartSelected)
* movableWidth / movableRange;
widthChange = isLeft ? -widthChange : widthChange;
MoveThumb(leftButton, rightButton, widthChange);
ReCalculateRangeSelected(true, true);
}
/// <summary>
/// Reset the Slider to the Start/End
/// </summary>
/// <param name="isStart">Pass true to reset to start point</param>
public void ResetSelection(bool isStart)
{
double widthChange = RangeStop - RangeStart;
widthChange = isStart ? -widthChange : widthChange;
MoveThumb(leftButton, rightButton, widthChange);
ReCalculateRangeSelected(true, true);
}
///<summary>
/// Change the range selected
///</summary>
///<param name="span">The steps</param>
public void MoveSelection(long span)
{
if (span > 0)
{
if (RangeStopSelected + span > RangeStop)
span = RangeStop - RangeStopSelected;
}
else
{
if (RangeStartSelected + span < RangeStart)
span = RangeStart - RangeStartSelected;
}
if (span != 0)
{
internalUpdate = true;//set flag to signal that the properties are being set by the object itself
RangeStartSelected += span;
RangeStopSelected += span;
ReCalculateWidths();
internalUpdate = false;//set flag to signal that the properties are being set by the object itself
OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(this));
}
}
/// <summary>
/// Sets the selected range in one go. If the selection is invalid, nothing happens.
/// </summary>
/// <param name="selectionStart">New selection start value</param>
/// <param name="selectionStop">New selection stop value</param>
public void SetSelectedRange(long selectionStart, long selectionStop)
{
long start = Math.Max(RangeStart, selectionStart);
long stop = Math.Min(selectionStop, RangeStop);
start = Math.Min(start, RangeStop - MinRange);
stop = Math.Max(RangeStart + MinRange, stop);
if (stop >= start + MinRange)
{
internalUpdate = true;//set flag to signal that the properties are being set by the object itself
RangeStartSelected = start;
RangeStopSelected = stop;
ReCalculateWidths();
internalUpdate = false;//set flag to signal that the properties are being set by the object itself
OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(this));
}
}
/// <summary>
/// Changes the selected range to the supplied range
/// </summary>
/// <param name="span">The span to zoom</param>
public void ZoomToSpan(long span)
{
internalUpdate = true;//set flag to signal that the properties are being set by the object itself
// Ensure new span is within the valid range
span = Math.Min(span, RangeStop - RangeStart);
span = Math.Max(span, MinRange);
if (span == RangeStopSelected - RangeStartSelected)
return; // No change
// First zoom half of it to the right
long rightChange = (span - (RangeStopSelected - RangeStartSelected)) / 2;
long leftChange = rightChange;
// If we will hit the right edge, spill over the leftover change to the other side
if (rightChange > 0 && RangeStopSelected + rightChange > RangeStop)
leftChange += rightChange - (RangeStop - RangeStopSelected);
RangeStopSelected = Math.Min(RangeStopSelected + rightChange, RangeStop);
rightChange = 0;
// If we will hit the left edge and there is space on the right, add the leftover change to the other side
if (leftChange > 0 && RangeStartSelected - leftChange < RangeStart)
rightChange = RangeStart - (RangeStartSelected - leftChange);
RangeStartSelected = Math.Max(RangeStartSelected - leftChange, RangeStart);
if (rightChange > 0) // leftovers to the right
RangeStopSelected = Math.Min(RangeStopSelected + rightChange, RangeStop);
ReCalculateWidths();
internalUpdate = false;//set flag to signal that the properties are being set by the object itself
OnRangeSelectionChanged(new RangeSelectionChangedEventArgs(this));
}
#endregion
//Raises the RangeSelectionChanged event
private void OnRangeSelectionChanged(RangeSelectionChangedEventArgs e)
{
e.RoutedEvent = RangeSelectionChangedEvent;
RaiseEvent(e);
}
/// <summary>
/// Overide to get the visuals from the control template
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
visualElementsContainer = EnforceInstance<StackPanel>("PART_RangeSliderContainer");
centerThumb = EnforceInstance<Thumb>("PART_MiddleThumb");
leftButton = EnforceInstance<RepeatButton>("PART_LeftEdge");
rightButton = EnforceInstance<RepeatButton>("PART_RightEdge");
leftThumb = EnforceInstance<Thumb>("PART_LeftThumb");
rightThumb = EnforceInstance<Thumb>("PART_RightThumb");
InitializeVisualElementsContainer();
ReCalculateWidths();
}
#region Helper
T EnforceInstance<T>(string partName)
where T : FrameworkElement, new()
{
T element = GetTemplateChild(partName) as T;
if (element == null)
element = new T();
return element;
}
//adds all visual element to the conatiner
private void InitializeVisualElementsContainer()
{
visualElementsContainer.Orientation = Orientation.Horizontal;
leftThumb.Width = DefaultSplittersThumbWidth;
leftThumb.Tag = "left";
rightThumb.Width = DefaultSplittersThumbWidth;
rightThumb.Tag = "right";
//handle the drag delta
centerThumb.DragDelta += CenterThumbDragDelta;
leftThumb.DragDelta += LeftThumbDragDelta;
rightThumb.DragDelta += RightThumbDragDelta;
leftButton.Click += LeftButtonClick;
rightButton.Click += RightButtonClick;
}
#endregion
}
/// <summary>
/// Delegate for the RangeSelectionChanged event
/// </summary>
/// <param name="sender">The object raising the event</param>
/// <param name="e">The event arguments</param>
public delegate void RangeSelectionChangedEventHandler(object sender, RangeSelectionChangedEventArgs e);
/// <summary>
/// Event arguments for the Range slider RangeSelectionChanged event
/// </summary>
public class RangeSelectionChangedEventArgs : RoutedEventArgs
{
private long newRangeStart;
/// <summary>
/// The new range start selected in the range slider
/// </summary>
public long NewRangeStart
{
get { return newRangeStart; }
set { newRangeStart = value; }
}
private long newRangeStop;
/// <summary>
/// The new range stop selected in the range slider
/// </summary>
public long NewRangeStop
{
get { return newRangeStop; }
set { newRangeStop = value; }
}
/// <summary>
/// sets the range start and range stop for the event args
/// </summary>
/// <param name="newRangeStart">The new range start set</param>
/// <param name="newRangeStop">The new range stop set</param>
internal RangeSelectionChangedEventArgs(long newRangeStart, long newRangeStop)
{
this.newRangeStart = newRangeStart;
this.newRangeStop = newRangeStop;
}
/// <summary>
/// sets the range start and range stop for the event args by using the slider RangeStartSelected and RangeStopSelected properties
/// </summary>
/// <param name="slider">The slider to get the info from</param>
internal RangeSelectionChangedEventArgs(RangeSlider slider)
: this(slider.RangeStartSelected, slider.RangeStopSelected)
{ }
}
{
#区域数据成员
bool internalUpdate=false;
const double RepeatButton平均值=0.1;//用于在单击重复按钮时按x比例移动选择
const double DefaultSplittersThumbWidth=10;
Thumb centerThumb;//移动范围的中心拇指
Thumb leftThumb;//用于扩展选定范围的左拇指
Thumb rightThumb;//用于扩展选定范围的右拇指
RepeatButton leftButton;//控件的左侧(可移动的左侧部分)
RepeatButton rightButton;//控件的右侧(可移动的右侧部分)
StackPanel visualElementsContainer;//用于存储此控件的可视元素的StackPanel
#端区
#区域属性和事件
///
///范围滑块范围的最小值
///
公共远程启动
{
获取{return(long)GetValue(RangeStartProperty);}
set{SetValue(RangeStartProperty,value);}
}
///
///范围滑块范围的最小值
///
公共静态只读从属属性RangeStartProperty=
DependencyProperty.Register(“RangeStart”、typeof(long)、typeof(RangeSlaider),
新的UIPropertyMetadata((长)0,
委托(DependencyObject发送方,DependencyPropertyChangedEventArgs e)
{
范围滑块=(范围滑块)发送器;
if(!slider.internalUpdate)//检查属性是否在内部设置
{
slider.recreacteranges();
滑块。重新计算宽度();
}
}));
///
///范围滑块范围的最大值
///
公共长途站
{
获取{return(long)GetValue(RangeStopProperty);}
set{SetValue(RangeStopProperty,value);}
}
///
///范围滑块范围的最大值
///
公共静态只读从属属性RangeStopProperty=
DependencyProperty.寄存器(“RangeStop”、typeof(长)、typeof(RangeSlaider),
新UIPropertyMetadata((长)1,
委托(DependencyObject发送方,DependencyPropertyChangedEventArgs e)
{
范围滑块=(范围滑块)发送器;
if(!slider.internalUpdate)//检查属性是否在内部设置
{
slider.recreacteranges();
滑块。重新计算宽度();
}
}));
///
///范围滑块选定范围的最小值
///
已选择公共远程启动
{
获取{return(long)GetValue(RangeStartSelectedProperty);}
set{SetValue(RangeStartSelectedProperty,value);}
}
///
///范围滑块选定范围的最小值
///
公共静态只读从属属性RangeStartSelectedProperty=
DependencyProperty.Register(“RangeStartSelected”、typeof(long)、typeof(RangeSlaider),
新的UIPropertyMetadata((长)0,
委托(DependencyObject发送方,DependencyPropertyChangedEventArgs e)
{
范围滑块=(范围滑块)发送器;
if(!slider.internalUpdate)//检查属性是否在内部设置
{
滑块。重新计算宽度();
slider.OnRangeSelectionChanged(新的RangeSelectionChangedEventArgs(slider));
}
}));
///
///范围滑块选定范围的最大值
///
选择公共长距离停车场
{
获取{return(long)GetValue(RangeStopSelectedProperty);}
set{SetValue(RangeStopSelectedProperty,value);}
}
///
///范围滑块选定范围的最大值
///
公共静态只读从属属性RangeStopSelectedProperty=
DependencyProperty.Register(“RangeStopSelected”、typeof(long)、typeof(RangeSlaider),
新UIPropertyMetadata((长)1,
委托(DependencyObject发送方,DependencyPropertyChangedEventArgs e)
{
范围滑块=(范围滑块)发送器;
if(!slider.internalUpdate)//检查属性是否在内部设置
{
滑块。重新计算宽度();
slider.OnRangeSelectionChanged(新的RangeSelectionChangedEventArgs(slider));
}
}));
///
///可用于范围滑块的最小范围值
///
///当MinRange设置为小于0时引发
公共长途电话
{
获取{return(long)GetValue(MinRangeProperty);}
set{SetValue(MinRangeProperty,value);}
}
///
///可用于范围滑块的最小范围值
///
///当MinRange设置为小于0时引发
公共静态只读从属属性MinRangeProperty=
DependencyProperty.Register(“MinRange”、typeof(long)、typeof(RangeSlider),
新的UIPropertyMetadata((长)0,
委托(DependencyObject发送方,DependencyPropertyChangedEventArgs e)
{
如果((长)e.NewValue<0)
抛出新ArgumentOutOfRangeException(“值”,“MinRange的值不能小于0”);
范围滑块sl