【问题标题】:MVVM Binding to Textbox: Need to call GET after SET somehowMVVM 绑定到文本框:需要在 SET 之后以某种方式调用 GET
【发布时间】:2013-01-16 16:54:59
【问题描述】:

我正在尝试在 MVVM 中的文本框上实现一些简单的验证

    public string Property
    {
        get
        {
            if (App.PropertyStorageContainer != null)
            {
                return App.PropertyStorageContainer.Property;
            }
            else
            {
                return null;
            }
        }
        set
        {
            App.PropertyStorageContainer.Property = value;
            RaisePropertyChanged("Property");
        }
    }

然后在我的 PropertyStorageContainer 类中我有

private string _property;
public string Property
    {
        get
        {               
            return App.PropertyStorageContainer.Property;
        }
        set
        {
            if(value meets some condition)
            {
                _property = value;
            }
            else
            {
               _property = someothervalue;
            }
        }
    }

.

   <TextBox Width="50" TextAlignment="Center" Text="{Binding Property, Mode=TwoWay, NotifyOnValidationError=True}" MaxLength="3"></TextBox>                          

这样做的目的是验证盒子里装了什么。现在,如果我直接从我的代码中设置这个值,那么一切都会按我的预期工作。它尝试 SET 值,然后调用 RaiseProperyChanged,然后 GET 值(因为验证可能与最初输入的值不同)。检索到的最终值确实显示在视图上,所以我知道 TwoWay 绑定正在工作。

我遇到的问题是当 SET 的输入来自用户绑定的 XAML 属性/直接时。在这种情况下,调用了 SET 方法,执行了验证,但 GET 永远不会发生。这会导致屏幕上的文本框中保留未验证的值。

我的第一个问题是这是一个错误还是预期的行为?我可以看到,当输入直接来自用户时,他们可能如何尝试通过删除最后一个 GET 来节省性能,因为 GET 应该没有什么新东西。但如果不是,那么我的所有设置方式可能会干扰 GET 被调用。

第二个问题当然是解决这个问题的任何建议。我已经阅读了一些关于其他验证方法的建议,但是我的程序已经在 PROD 上运行,并且建议的大多数更改都涉及到我的大量返工,所以我希望找到一种方法让它调用 GET any设置属性的时间。

【问题讨论】:

    标签: c# data-binding mvvm silverlight-4.0 inotifypropertychanged


    【解决方案1】:

    我做了几个假设,因为我不确定我是否完全理解您的代码,但我认为您可以考虑实施自定义验证规则。首先,由于您的自定义 ValidationRule 将负责验证,您可以从模型类的属性定义中获取逻辑并“简化”您的 poco:

    class PropertyStorageContainer
    {
        public string Property { get; set; }
    }
    

    您似乎希望您的视图模型充当模型类的基本包装器。同样,我会根据您对场景的描述假设这是有效的:

    class PropertyStorageContainerViewModel : INotifyPropertyChanged
    {
        private PropertyStorageContainer model;
    
        public PropertyStorageContainerViewModel(PropertyStorageContainer model)
        {
            this.model = model;
        }
    
        public string Property
        {
            get
            {
                if (model != null)
                {
                    return model.Property;
                }
                else
                {
                    return null;
                }
            }
            set
            {
                if (model.Property != value)
                {
                    model.Property = value;
                    RaisePropertyChanged("Property");
                }
            }
        } 
        // INotifyPropertyChanged implementation...
    }
    

    现在创建一个扩展 System.Windows.Controls.ValidationRule 的新类并覆盖抽象的 Validate 方法以实现您的验证逻辑。例如,我创建了一个规则来检查字符串是空还是空(假设这将是一个无效的场景):

    class IsNullOrEmptyValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            string s = (value ?? string.Empty).ToString();
            if (string.IsNullOrEmpty(s))
            {
                // Invalid...
                return new ValidationResult(false, "Please enter a value.");
            }
            else
            {
                // Valid...
               return new ValidationResult(true, null);
            }
        }
    }
    

    现在是 XAML... 下面是一个将验证规则添加到其绑定验证规则(可以是多个规则)的 TextBox 示例。

     <TextBox Name="textBox1" Width="50" FontSize="12"
            Validation.ErrorTemplate="{StaticResource validationTemplate}"
            Style="{StaticResource textBoxInError}">
        <TextBox.Text>
            <Binding Path="Property" UpdateSourceTrigger="PropertyChanged" >
                <Binding.ValidationRules>
                    <local:IsNullOrEmptyValidationRule />
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox> 
    

    然后在某处(例如,Window.Resources)定义以下资源(上面引用)。首先是一个 ControlTemplate 来定义 TextBox 在无效状态下的外观:

    <ControlTemplate x:Key="validationTemplate">
        <DockPanel>
            <TextBlock Foreground="Red" FontSize="15" Text="!!!" />
            <AdornedElementPlaceholder/>
        </DockPanel>
    </ControlTemplate>
    

    此外,您可以定义样式触发器来显示错误消息。这里我只是将它绑定到 TextBox 的 ToolTip 属性:

    <Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip" 
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    

    【讨论】:

    • 感谢您的帮助,但我最终弄清楚了如何使用我已有的代码让 RaisePropertyChanged 工作。基本上它归结为.NET 不想允许来自更新绑定的同一线程的 RaisePropertyChanged。这是有道理的,因为当您是开始更改属性的人时,为什么要寻找更新的属性。无论如何,我通过调用 Dispatcher.BeginInvoke(() => _viewmodel.RaisePropertyChanged(null));从而分离出线程
    • 我再次花时间提供一个正统的解决方案,却发现它没有被标记为已回答,并且非正统/“快速修复”路线仍然被视为正确的道路下。哦,好吧...
    • @blins 这里至少是我的+1...别担心他以后会后悔自己的决定;)
    • 嗯,您没有被标记为已回答的原因是因为我的问题没有要求正统的解决方案。我要求解释这种行为,以及可能的解决方法。实现这一点的应用程序在 1000 多个页面上使用了相同的技术,每个页面都有数百个绑定,管理人员可能不太关心这个错误。有时需要快速修复。
    • 但是,如果它意味着这么多,我会把它给你,因为你确实很好地解释了如何正确实施这种类型的验证。
    【解决方案2】:

    你现在正进入 INPC 地狱。我去过那里,但并不好玩。
    这是一个很大的禁忌,特别是因为如果对此类类进行任何映射,则这些 getter 和 setter 将在其 WPF 绑定上下文之外被调用,并且会失败。

    保持简单:直接绑定到App.PropertyStorageContainer.Property

    对于第二种情况,要么:

    • 使用数据验证
    • 让属性不是通过绑定而是通过命令来设置,您可以在其中进行这种值交换。

    帮自己一个忙,不要滥用属性的获取/设置

    【讨论】:

    • 直接绑定到 App.PropertyStorageContainer.Property 并不能解决问题。它仍然调用SET而不调用GET,这是问题的核心。
    • 你是什么意思使用数据验证?这不是很具有描述性。此外,不希望通过命令设置属性,因为这不再是 MVVM。
    • @Josh 我的意思是,以这种方式使用 getter/setter 是糟糕的设计,并且会导致更多的设计问题。并且为此使用命令是完美的 MVVMish,只要您不只是设置它而是对数据采取行动(if(value meets some condition)
    • 您肯定意识到在 SET 之后调用 GET 很奇怪,而且您不会期望其他人编写的软件会这样做。
    • 我不想调用 GET 和 SET,它应该在访问绑定属性时自动发生!无论如何,在另一个使用过的人的帮助下,我已经弄清楚了。我最终在 SET 结束时调用了一个事件,该事件调用视图,然后调用 Dispatcher.BeginInvoke(() => _viewmodel.RaisePropertyChanged(null));调度程序是必要的,因为 .NET 会尽一切努力阻止 UI 在不需要时调用 GET,因此从更新绑定开始的同一线程中删除此调用至关重要。
    猜你喜欢
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 2021-03-01
    • 2014-11-29
    • 2015-10-16
    • 1970-01-01
    • 2012-11-24
    相关资源
    最近更新 更多