C# MVVM绑定到文本框:需要在SET之后调用GET

C# MVVM绑定到文本框:需要在SET之后调用GET,c#,data-binding,mvvm,silverlight-4.0,inotifypropertychanged,C#,Data Binding,Mvvm,Silverlight 4.0,Inotifypropertychanged,我试图在MVVM中的文本框上实现一些简单的验证 public string Property { get { if (App.PropertyStorageContainer != null) { return App.PropertyStorageContainer.Property; } else

我试图在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;
            }
        }
    }


这一点是为了验证盒子里的东西。现在,如果我直接从我的代码中设置这个值,那么一切都会像我预期的那样工作。它尝试设置该值,然后调用RaisePropertyChanged,然后获取该值(由于验证,该值可能与最初输入的值不同)。检索到的最终值确实显示在视图中,因此我知道双向绑定正在工作

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

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


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

你现在就要进INPC的地狱了。我去过那里,那里一点也不好玩。
这是一个很大的禁忌,特别是因为如果在这样的类上进行任何映射,那么这些getter和setter将在WPF绑定上下文之外被调用,并且会丢失

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

第二种情况是:

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

帮自己一个忙,不要滥用属性'GET/SET/

< P>我已经做了一些假设,因为我不确定我完全理解你的代码,但是我认为你可以考虑可能实现一个自定义的验证规则。首先,由于自定义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...
}
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);
        }
    }
}
现在创建一个扩展System.Windows.Controls.ValidationRule的新类,并重写抽象验证方法以实现验证逻辑。例如,我创建了一个规则,只检查字符串是否为null或空(假设这是一个无效的场景):

现在是XAML。。。下面是一个文本框示例,它将验证规则添加到其绑定验证规则(可以是多个规则)


然后在某个地方(例如,Window.resources)定义以下资源(如上所述)。首先使用ControlTemplate定义文本框在无效状态下的外观:

<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>


直接绑定到App.PropertyStorageContainer.Property不会解决任何问题。它仍然调用SET而不调用GET,这是问题的核心。使用数据验证是什么意思?这不是很好的描述。另外,通过命令设置属性也是不可取的,因为它不再是MVVM。@Josh我的意思是,以这种方式使用getter/setter是一种糟糕的设计,会导致更多的设计问题。使用命令来实现这一点是完美的MVVMish,只要你不只是设置它,而且还对数据起作用(
如果(值满足某些条件)
),你肯定会意识到在SET之后调用GET是很奇怪的,你不会期望其他人编写的软件会这样做。我不想调用GET和SET,它应该在访问绑定属性时自动发生!无论如何,在另一个用过的人的帮助下,我找到了答案。我最后在集合的末尾调用了一个事件,该事件调用视图,然后调用Dispatcher.BeginInvoke(()=>_viewmodel.RaisePropertyChanged(null));dispatcher是必要的,因为.NET将尽一切努力阻止UI在不必要的情况下调用GET,因此从更新绑定的同一线程中删除此调用至关重要。感谢您的帮助,但我最终找到了如何使用我已有的代码使RaisePropertyChange正常工作的方法。基本上,它归结为.NET不希望允许从更新绑定的同一线程更改RaiseProperty。这是有意义的,因为当您首先更改属性时,为什么要查找更新的属性。无论如何,我通过调用Dispatcher.BeginInvoke(()=>\u viewmodel.RaisePropertyChanged(null))绕过了这个问题;因此,我再次花时间提供一个正统的解决方案,结果发现它没有被标记为已回答,而非正统的/快速修复的路线仍然被视为正确的路径。哦,好吧…@blins我的+1至少在这里。。。别担心,他以后会后悔他的决定;)你没有被标记为回答的原因是因为我的问题没有要求一个正统的解决方案。我要求解释一下这种行为,以及可能的解决办法。该应用程序在1000多页wi上使用了相同的技术
<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="15" Text="!!!" />
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>
<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>