WPF:如果 UI 元素不可见,则停止绑定

Posted

技术标签:

【中文标题】WPF:如果 UI 元素不可见,则停止绑定【英文标题】:WPF: Stop Binding if a UI element is not visible 【发布时间】:2011-01-15 01:49:15 【问题描述】:

如果元素当前不可见,我可以延迟绑定 ui 元素吗?有时我有一个包含一些隐藏/最小化元素的表单,如果它们不在屏幕上,我不想更新它们。我怀疑答案是否定的,但问也无妨?

【问题讨论】:

你为什么要这样做?为了表现? 是的性能,它实际上是懒惰(懒惰是发明之母),因为如果它们不可见,我应该从树中删除它们,以获得我需要的性能。 【参考方案1】:

答案是否定的,因为绑定可能导致元素再次可见。因此,如果绑定在隐藏控件上不起作用,它将不允许绑定再次使其可见。

【讨论】:

【参考方案2】:

我知道这是一个老问题,但由于我未能找到已实现的类或其他东西,我自己按照@Nir 的回答做了。

这是一个标记扩展,它将普通绑定包装为仅在对象IsVisible 属性第一次变为真时才真正绑定:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace MakupExtensions 
    [MarkupExtensionReturnType(typeof(object))]
    public class LazyBindingExtension : MarkupExtension 
        public LazyBindingExtension() 
        
        public LazyBindingExtension(PropertyPath path) : this() 
            Path = path;
        

        public IValueConverter Converter 
            get;
            set;
        
        [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
        public CultureInfo ConverterCulture 
            get;
            set;
        
        public object ConverterParamter 
            get;
            set;
        
        public string ElementName 
            get;
            set;
        
        [ConstructorArgument("path")]
        public PropertyPath Path 
            get;
            set;
        
        public RelativeSource RelativeSource 
            get;
            set;
        
        public object Source 
            get;
            set;
        
        public UpdateSourceTrigger UpdateSourceTrigger 
            get;
            set;
        
        public bool ValidatesOnDataErrors 
            get;
            set;
        
        public bool ValidatesOnExceptions 
            get;
            set;
        
        public bool ValidatesOnNotifyDataErrors 
            get;
            set;
        

        private Binding binding;
        private DependencyObject bindingTarget;
        private DependencyProperty bindingTargetProperty;

        public override object ProvideValue(IServiceProvider serviceProvider) 
            var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (valueProvider != null) 
                bindingTarget = valueProvider.TargetObject as DependencyObject;
                bindingTargetProperty = valueProvider.TargetProperty as DependencyProperty;
                if (bindingTargetProperty == null || bindingTarget == null) 
                    throw new NotSupportedException($"The property 'valueProvider.TargetProperty' on target 'valueProvider.TargetObject' is not valid for a LazyBinding. The LazyBinding target must be a DependencyObject, and the target property must be a DependencyProperty.");
                
                binding = new Binding 
                    Path = Path,
                    Converter = Converter,
                    ConverterCulture = ConverterCulture,
                    ConverterParameter = ConverterParamter
                ;
                if (ElementName != null) 
                    binding.ElementName = ElementName;
                
                if (RelativeSource != null) 
                    binding.RelativeSource = RelativeSource;
                
                if (Source != null) 
                    binding.Source = Source;
                
                binding.UpdateSourceTrigger = UpdateSourceTrigger;
                binding.ValidatesOnDataErrors = ValidatesOnDataErrors;
                binding.ValidatesOnExceptions = ValidatesOnExceptions;
                binding.ValidatesOnNotifyDataErrors = ValidatesOnNotifyDataErrors;
                return SetBinding();
            
            return null;
        
        public object SetBinding() 
            var uiElement = bindingTarget as UIElement;
            if (uiElement != null && !uiElement.IsVisible) 
                uiElement.IsVisibleChanged += UiElement_IsVisibleChanged;
            
            else 
                ConsolidateBinding();
            
            return bindingTarget.GetValue(bindingTargetProperty);
        
        private void ConsolidateBinding() => BindingOperations.SetBinding(bindingTarget, bindingTargetProperty, binding);
        private void UiElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) 
            var uiElement = sender as UIElement;
            if (uiElement != null && uiElement.IsVisible) 
                uiElement.IsVisibleChanged -= UiElement_IsVisibleChanged;
                ConsolidateBinding();
            
        
    

使用方法:

<ItemsControl ItemsSource="mx:LazyBinding Documents"/>

在这个例子中,它只会在 ItemsControl IsVisible 第一次变为真时绑定。

IsVisible 再次变为 false 时不会解除绑定,但我认为有人可以根据需要更改它。

【讨论】:

【参考方案3】:

没有内置的方法可以做到这一点 - 但您可以自己编写。

诀窍是将绑定包装在您自己的标记扩展中,该扩展使用原始绑定但在其周围添加新行为(例如,当您不希望绑定起作用时,通过将 UpdateSourceTrigger 设置为 Explicit。

这是一个示例(延迟绑定的数据传输):

http://www.paulstovell.com/wpf-delaybinding

现在,有很多可能的边缘条件禁用不可见控件的绑定,尤其是在显示和隐藏控件方面,所以我不会为此编写通用扩展 - 但也许在您的特定应用程序中这可能很有用。

【讨论】:

这很奇怪,我写了一个类似的东西 - codeproject.com/KB/WPF/DelayedBindingTextBox.aspx【参考方案4】:

对于一种解决方法,我绑定了对象的可见性,当对象设置为可见时,该属性会触发其后面的元素的构造,该元素通过ContentPresenter 进行绑定。

【讨论】:

【参考方案5】:

改进的 MarkupExtension 将普通绑定包装到自动绑定/取消绑定数据模型(如果可见已更改)。 参见之前的版本here。

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace UtilsWPF

    [MarkupExtensionReturnType(typeof(object))]
    public class LazyBindingExtension : MarkupExtension
    
        public LazyBindingExtension()
         

        public LazyBindingExtension(PropertyPath path) : this()
        
            Path = path;
        

        #region Properties

        public IValueConverter Converter  get; set; 
        [TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
        public CultureInfo ConverterCulture  get; set; 
        public object ConverterParamter  get; set; 
        public string ElementName  get; set; 
        [ConstructorArgument("path")]
        public PropertyPath Path  get; set; 
        public RelativeSource RelativeSource  get; set; 
        public object Source  get; set; 
        public UpdateSourceTrigger UpdateSourceTrigger  get; set; 
        public bool ValidatesOnDataErrors  get; set; 
        public bool ValidatesOnExceptions  get; set; 
        public bool ValidatesOnNotifyDataErrors  get; set; 

        private Binding binding;
        private UIElement bindingTarget;
        private DependencyProperty bindingTargetProperty;

        #endregion

        #region Init

        public override object ProvideValue(IServiceProvider serviceProvider)
        
            var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (valueProvider != null)
            
                bindingTarget = valueProvider.TargetObject as UIElement;

                if (bindingTarget == null)
                
                    throw new NotSupportedException($"Target 'valueProvider.TargetObject' is not valid for a LazyBinding. The LazyBinding target must be a UIElement.");
                

                bindingTargetProperty = valueProvider.TargetProperty as DependencyProperty;

                if (bindingTargetProperty == null)
                
                    throw new NotSupportedException($"The property 'valueProvider.TargetProperty' is not valid for a LazyBinding. The LazyBinding target property must be a DependencyProperty.");
                

                binding = new Binding
                
                    Path = Path,
                    Converter = Converter,
                    ConverterCulture = ConverterCulture,
                    ConverterParameter = ConverterParamter
                ;

                if (ElementName != null)
                
                    binding.ElementName = ElementName;
                

                if (RelativeSource != null)
                
                    binding.RelativeSource = RelativeSource;
                

                if (Source != null)
                
                    binding.Source = Source;
                

                binding.UpdateSourceTrigger = UpdateSourceTrigger;
                binding.ValidatesOnDataErrors = ValidatesOnDataErrors;
                binding.ValidatesOnExceptions = ValidatesOnExceptions;
                binding.ValidatesOnNotifyDataErrors = ValidatesOnNotifyDataErrors;

                return SetBinding();
            

            return null;
        

        public object SetBinding()
        
            bindingTarget.IsVisibleChanged += UiElement_IsVisibleChanged;

            updateBinding();

            return bindingTarget.GetValue(bindingTargetProperty);
        

        #endregion

        #region Event Handlers

        private void UiElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        
            updateBinding();
        

        #endregion

        #region Update Binding

        private void updateBinding()
        
            if (bindingTarget.IsVisible)
            
                ConsolidateBinding();
            
            else
            
                ClearBinding();
            
        

        private bool _isBind;

        private void ConsolidateBinding()
        
            if (_isBind)
            
                return;
            

            _isBind = true;

            BindingOperations.SetBinding(bindingTarget, bindingTargetProperty, binding);
        

        private void ClearBinding()
        
            if (!_isBind)
            
                return;
            

            BindingOperations.ClearBinding(bindingTarget, bindingTargetProperty);

            _isBind = false;
        

        #endregion
    

使用方法:

<ItemsControl ItemsSource="utils:LazyBinding Documents"/>

【讨论】:

以上是关于WPF:如果 UI 元素不可见,则停止绑定的主要内容,如果未能解决你的问题,请参考以下文章

WPF DataGrid的可见性

如何使用 WPF 中的代码绑定 DataGridTextColumn 的可见性属性?

WPF DataGrid的可见性

使用绑定从片段访问父活动的 UI 元素

WPF 绑定不更新可见性

WPF:元素绑定