Two-way-binding: editing passed value from XAML co

2019-07-23 22:19发布

问题:

This is for a Windows 10 Universal App.

XAML:

<RelativePanel Padding="4" Margin="4,12,0,0">
     <TextBlock x:Name="Label" Text="Class Name" Margin="12,0,0,4"/>
       <ListView x:Name="ClassTextBoxes" 
                 ItemsSource="{Binding TextBoxList}" 
                 SelectionMode="None" RelativePanel.Below="Label">
            <ListView.ItemTemplate>
              <DataTemplate >
                <RelativePanel>
                  <TextBox x:Name="tbox" 
                       PlaceholderText="{Binding PlaceHolder}" 
                       Text="{Binding BoxText, 
                       Mode=TwoWay,
                       UpdateSourceTrigger=PropertyChanged}" 
                       Padding="4" Width="200" MaxLength="25"/>
                  <TextBlock x:Name="errorLabel" 
                       RelativePanel.Below="tbox"
                       Text="{Binding Error, Mode=TwoWay}"
                       Padding="0,0,0,4"
                       FontSize="10" 
                       Foreground="Red"/>
                  <Button Content="Delete" Margin="12,0,0,0" RelativePanel.RightOf="tbox"/>
              </RelativePanel>
            </DataTemplate>
          </ListView.ItemTemplate>
       </ListView>
</RelativePanel>

Model:

public class TextBoxStrings : BaseModel
{
    private string _placeholder;
    public string PlaceHolder
    {
        get { return _placeholder; }
        set
        {
            if (_placeholder != value)
            {
                _placeholder = value;
                NotifyPropertyChanged();
            }
        }
    }
    private string _boxText;
    public string BoxText
    {
        get { return _boxText; }
        set
        {
            if (_boxText != value)
            {
                _boxText = CheckBoxText(value);
                NotifyPropertyChanged();
            }
        }
    }

    public string CheckBoxText(string val)
    {
        var r = new Regex("[^a-zA-Z0-9]+");
        return r.Replace(val, "");
    }
}

ViewModel:

private TrulyObservableCollection<TextBoxStrings> _textBoxList;
public TrulyObservableCollection<TextBoxStrings> TextBoxList
{
        get { return _textBoxList; }
        set
        {
            if (_textBoxList != value)
            {
                _textBoxList = value;
                RaisePropertyChanged();
            }
        }
}

and I add new TextBoxString objects to my TextBoxList collection from within my view-model.

I want to make it that users can't type in certain characters (or rather, they get deleted whenever they are typed in.

This works...in the model. Setting breakpoints and looking at the values, everything in the Model is working: value goes into the setter and gets changed, _boxText holds the new value that is set from CheckBoxText();

But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.

So if I type in "abc*()" into "tbox", the value in the model will be "abc". The value of the textbox, however, will still be "abc*()".

I have a feeling it has something to do with the fact that I'm editing items that are inside of a collection and I don't have anything implemented to handle changing items within a collection. I was under the impression that using INotifyPropertyChanged and ObservableCollection<T> would take care of that for me.

Does anyone have any suggestions?

Thank you!

Edit: So, now I'm trying to use TrulyObservableCollection because I thought this was the problem, but it hasn't helped. Here it is: https://gist.github.com/itajaja/7507120

回答1:

But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.

As you've seen, the TextBox do reflect changes to your model. When you type in "abc*()" in the TextBox, the value in the model will be changed to "abc". The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.

In WPF, we can call BindingExpression.UpdateTarget Method to force the update. But this method is not available in UWP.

As a workaround, you should be able to use TextBox.TextChanged event to check the input like following:

private void tbox_TextChanged(object sender, TextChangedEventArgs e)
{
    var tb = sender as TextBox;
    if (tb != null)
    {
        var originalText = tb.Text;

        var r = new Regex("[^a-zA-Z0-9]+");
        if (originalText != r.Replace(originalText, ""))
        {
            var index = (tb.SelectionStart - 1) < 0 ? 0 : (tb.SelectionStart - 1);
            tb.Text = r.Replace(originalText, "");
            tb.SelectionStart = index;
        }
    }
}

However it may break your MVVM model, you can use data validation to avoid this and here is a blog: Let’s Code! Handling validation in your Windows Store app (WinRT-XAML) you can refer to. And for my personal opinion, data validation is a better direction for this scenario.



回答2:

if (_boxText != value)
{
    _boxText = CheckBoxText(value);
    NotifyPropertyChanged();
}

Try changing this to:

var tmp = CheckBoxText(value);
if (_boxText != tmp)
{
    _boxText = tmp;
    NotifyPropertyChanged();
}

I hope, in your XAML, the binding to property BoxText is two-way, right?



回答3:

You should edit BoxText and then send checked value to UI. Just send value to CheckBoxText and already edited should be assigned to _boxText. And then you should send BoxText to UI by calling RaisePropertyChanged("BoxTest"). Please, see the following code snippet:

private string _boxText;
public string BoxText
{
    get { return _boxText; }
    set
    {
        if (_boxText != value)
        {                    
            _boxText=CheckBoxText(value);
            RaisePropertyChanged("BoxText");
        }
    }
}

There is no difference where you use INotifyPropertyChanged for one property of for properties placed in collection. The complete example with collections and ListView can be seen here