如果可以更改 VisualParent,如何在 UserControl 中处理订阅
How to Dispose subscriptions in a UserControl if you can change the VisualParent
我有一个 FooUserControl
订阅了 LoadedEvent
。这个 UserControl
可以放在你的 gui 的其他地方(在任何 Window
或任何 Control
的内部)。为了避免泄漏,我实施了某种处理。
此方案存在的问题:
如果将 FooUserControl
放在 TabControl
的 TabItem
上并更改选项卡,则会调用 OnVisualParentChanged()
并处理订阅。如果我不添加此方法,并且您关闭了 TabItem
,则订阅在后台仍然有效,但可以处理 UserControl
。 page
也会出现同样的问题
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
public FooUserControl()
{
Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
var parentWindow = Window.GetWindow(this);
if(parentWindow != null)
parentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
// check if the parent visual has been changed
// can happen if you use the control on a page
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (oldParent != null)
{
_Dispose();
}
base.OnVisualParentChanged(oldParent);
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
我终于找到了解决办法。在 UnLoaded
事件中,我扫描 Logical/VisualTree
是否仍然存在实例。
由于wpf
中没有真正的disposing
机制,所以我采用了这个方案。我愿意寻求更好的解决方案!
FooUserControl
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
private Window _ParentWindow;
public FooUserControl()
{
Loaded += _OnLoaded;
Unloaded += _OnUnloaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
_ParentWindow = Window.GetWindow(this);
_ParentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _OnUnloaded(object sender, RoutedEventArgs e)
{
// look in logical and visual tree if the control has been removed
if (_ParentWindow.FindChildByUid<NLogViewer>(Uid) == null)
{
_Dispose();
}
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
DependencyObjectExtensions
public static class DependencyObjectExtensions
{
/// <summary>
/// Analyzes both visual and logical tree in order to find all elements of a given
/// type that are descendants of the <paramref name="source"/> item.
/// </summary>
/// <typeparam name="T">The type of the queried items.</typeparam>
/// <param name="source">The root element that marks the source of the search. If the
/// source is already of the requested type, it will not be included in the result.</param>
/// <param name="uid">The UID of the <see cref="UIElement"/></param>
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
public static T FindChildByUid<T>(this DependencyObject source, string uid) where T : UIElement
{
if (source != null)
{
var childs = GetChildObjects(source);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T dependencyObject && dependencyObject.Uid.Equals(uid))
{
return dependencyObject;
}
var descendant = FindChildByUid<T>(child, uid);
if (descendant != null)
return descendant;
}
}
return null;
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Keep in mind that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <returns>The submitted item's child elements, if available.</returns>
public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
{
if (parent == null) yield break;
if (parent is ContentElement || parent is FrameworkElement)
{
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null) yield return (DependencyObject) obj;
}
}
else
{
//use the visual tree per default
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
}
我有一个 FooUserControl
订阅了 LoadedEvent
。这个 UserControl
可以放在你的 gui 的其他地方(在任何 Window
或任何 Control
的内部)。为了避免泄漏,我实施了某种处理。
此方案存在的问题:
如果将 FooUserControl
放在 TabControl
的 TabItem
上并更改选项卡,则会调用 OnVisualParentChanged()
并处理订阅。如果我不添加此方法,并且您关闭了 TabItem
,则订阅在后台仍然有效,但可以处理 UserControl
。 page
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
public FooUserControl()
{
Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
var parentWindow = Window.GetWindow(this);
if(parentWindow != null)
parentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
// check if the parent visual has been changed
// can happen if you use the control on a page
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (oldParent != null)
{
_Dispose();
}
base.OnVisualParentChanged(oldParent);
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
我终于找到了解决办法。在 UnLoaded
事件中,我扫描 Logical/VisualTree
是否仍然存在实例。
由于wpf
中没有真正的disposing
机制,所以我采用了这个方案。我愿意寻求更好的解决方案!
FooUserControl
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
private Window _ParentWindow;
public FooUserControl()
{
Loaded += _OnLoaded;
Unloaded += _OnUnloaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
_ParentWindow = Window.GetWindow(this);
_ParentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _OnUnloaded(object sender, RoutedEventArgs e)
{
// look in logical and visual tree if the control has been removed
if (_ParentWindow.FindChildByUid<NLogViewer>(Uid) == null)
{
_Dispose();
}
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
DependencyObjectExtensions
public static class DependencyObjectExtensions
{
/// <summary>
/// Analyzes both visual and logical tree in order to find all elements of a given
/// type that are descendants of the <paramref name="source"/> item.
/// </summary>
/// <typeparam name="T">The type of the queried items.</typeparam>
/// <param name="source">The root element that marks the source of the search. If the
/// source is already of the requested type, it will not be included in the result.</param>
/// <param name="uid">The UID of the <see cref="UIElement"/></param>
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
public static T FindChildByUid<T>(this DependencyObject source, string uid) where T : UIElement
{
if (source != null)
{
var childs = GetChildObjects(source);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T dependencyObject && dependencyObject.Uid.Equals(uid))
{
return dependencyObject;
}
var descendant = FindChildByUid<T>(child, uid);
if (descendant != null)
return descendant;
}
}
return null;
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Keep in mind that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <returns>The submitted item's child elements, if available.</returns>
public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
{
if (parent == null) yield break;
if (parent is ContentElement || parent is FrameworkElement)
{
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null) yield return (DependencyObject) obj;
}
}
else
{
//use the visual tree per default
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
}