2011-02-27 1 views
2

저는 AvalonControls RangeSlider를 사용하여 DataTemplate에서 사용될 때 상당히 많은 메모리 누출을 발견했습니다. 나는 이것을 연구 한 다음이 범인이라고 결론을 내렸다 : 내가이 누수를 해결할 가정 RemoveValue이 변경에 대한 RangeSlider 클래스와 적절한 말했다AvalonControls RangeSlider 메모리 누수 (DependencyPropertyDescriptor ... AddValueChanged)

DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(RangeSlider)). 
      RemoveValueChanged(this, delegate { ReCalculateWidths(); }); 

, 이해가 안, . 탭에서 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) 
    { } 





} 

}

답변

0

나는 비슷한 문제를 가지고 있었다. 이 문서에서는 이유와 해결 방법을 설명합니다. http://sharpfellows.com/post/Memory-Leaks-and-Dependency-Properties.aspx

+0

누구나 테스트를 통과 했습니까? 내가 알 수있는 한, 여전히 문제를 해결하지 못할 것이다. 이유 : RemoveValueChanged는 FooProperty의 값이 변경 될 때만 호출됩니다. 이것이 일어나지 않으면 당신은 여전히 ​​누수가 있습니다. – SACO