【问题标题】:WPF MVVM How to prefill textbox with binding on ShowDialog()?WPF MVVM 如何使用 ShowDialog() 上的绑定预填充文本框?
【发布时间】:2020-01-08 13:01:44
【问题描述】:

我有一个带有ShowDialog() 的表单窗口,文本框绑定到视图模型中的属性。 我这样打开我的对话框(简化版):

FilterWindowView wnd = new FilterWindowView();
FilterWindowViewModel fvm = new FilterWindowViewModel(licenseRecords) { wnd = wnd };
wnd.DataContext = fvm;
fvm.RestoreCurrentFilters();
if (wnd.ShowDialog() ?? false)
{
    //...
}

我在表单中设置的属性用作过滤器参数,我将其存储在静态类中以供以后使用。

我想做的是让文本框自动填充存储在这个静态类中的当前值。

我的文本框绑定属性如下所示:

    private string _product;
    public string product
    {
        get { return _product; }
        set
        {
            if (_product == value)
                return;
            _product = value;
            Helper.product = value;
            if (value != "")
                chkProduct = true;
            OnPropertyChanged();
        }
    }

(我认为在验证时重新分配可能会更好,但这是另一个问题......) 我的问题是,如果我设置了一个值(即在构造函数中),该值会被设置,但在调用ShowDialog() 时,该值会重置为“”。

还尝试在实例化 VM 后调用方法,但如前所述,此重置发生在显示窗口时(调用 ShowDialog() 时)...

这个表单会生成一个自定义对象,我在 VM dialogResult 中恢复,所以转到 wnd.Show(),然后设置为存储的值对我来说不是一个选项(我猜?)。

感谢您的帮助。

编辑视图非常简单,只有几个标签和文本框以两种方式绑定到虚拟机。

<Window x:Class="LicenseManager.View.FilterWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:LicenseManager.View"
    mc:Ignorable="d"
    Title="FilterWindowView" Height="306.412" Width="284.216">
<Grid>
    <CheckBox Content="Product" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" IsChecked="{Binding chkProduct}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,8,0,0" TextWrapping="Wrap" Text="{Binding product, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Client" HorizontalAlignment="Left" Margin="10,38,0,0" VerticalAlignment="Top" IsChecked="{Binding chkClient}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,36,0,0" TextWrapping="Wrap" Text="{Binding client, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Date After" HorizontalAlignment="Left" Margin="10,66,0,0" VerticalAlignment="Top" IsChecked="{Binding chkDateAfter}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,64,0,0" TextWrapping="Wrap" Text="{Binding dateAfter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Date Before" HorizontalAlignment="Left" Margin="10,94,0,0" VerticalAlignment="Top" IsChecked="{Binding chkDateBefore}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,92,0,0" TextWrapping="Wrap" Text="{Binding dateBefore, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Sbs__no" HorizontalAlignment="Left" Margin="10,122,0,0" VerticalAlignment="Top" IsChecked="{Binding chkSbsNo}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,120,0,0" TextWrapping="Wrap" Text="{Binding sbsNo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Store__no" HorizontalAlignment="Left" Margin="10,150,0,0" VerticalAlignment="Top" IsChecked="{Binding chkStoreNo}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,148,0,0" TextWrapping="Wrap" Text="{Binding storeNo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Workstation__no" HorizontalAlignment="Left" Margin="10,178,0,0" VerticalAlignment="Top" IsChecked="{Binding chkWorkstationNo}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,176,0,0" TextWrapping="Wrap" Text="{Binding workstationNo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <CheckBox Content="Comment" HorizontalAlignment="Left" Margin="10,206,0,0" VerticalAlignment="Top" IsChecked="{Binding chkWorkstationNo}"/>
    <TextBox HorizontalAlignment="Left" Height="23" Margin="137,204,0,0" TextWrapping="Wrap" Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>
    <Button Content="Apply" Command="{Binding apply}" HorizontalAlignment="Left" Margin="10,236,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>

EDIT 视图模型类 Helper 是我的静态类

    class FilterWindowViewModel : INotifyPropertyChanged
        {
            #region Attributes
            public Window wnd; // For dialog closer
            public List<LicenseRecordModel> list;
            public List<LicenseRecordModel> dialogResult;
            public event PropertyChangedEventHandler PropertyChanged;

            string tmpProduct;
            string tmpClient;
            string tmpDateAfter;
            string tmpDateBefore;
            string tmpSbsNo;
            string tmpStoreNo;
            string tmpWorkstationNo;
            string tmpComment;
            #endregion

            #region Properties
            //Properties and commands
        private string _comment;
        public string comment
        {
            get { return _comment; }
            set
            {
                if (_comment == value)
                    return;
                _comment = value;
                Helper.comment = value;
                if (value != "")
                    chkComment = true;
                OnPropertyChanged();
            }
        }


//...
        private DelegateCommand _apply;
        public DelegateCommand apply
        {
            get
            {
                return _apply ?? (_apply = new DelegateCommand(o => Apply(), o => true));
            }
        }
            #endregion

            #region Init
            public FilterWindowViewModel(IEnumerable<LicenseRecordModel> source)
            {
                tmpProduct = Helper.product;
                tmpClient = Helper.client;
                tmpDateAfter = Helper.dateAfter;
                tmpDateBefore= Helper.dateBefore;
                tmpSbsNo = Helper.sbsNo;
                tmpStoreNo = Helper.storeNo;
                tmpWorkstationNo = Helper.workstationNo;
                tmpComment = Helper.comment;
                list = new List<LicenseRecordModel>(source);
            }

            public void RestoreCurrentFilters()
            {
                product = tmpProduct;
                client = tmpClient;
                dateAfter = tmpDateAfter;
                dateBefore = tmpDateBefore;
                sbsNo = tmpSbsNo;
                storeNo = tmpStoreNo;
                workstationNo = tmpWorkstationNo;
                comment = tmpComment;
            }

            protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChangedEventHandler handler = PropertyChanged;

                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion

            private bool Accept(LicenseRecordModel lic)
            {
                var tmp = list;
                tmp = list.Where(x => 
                chkProduct ? x.Product.Contains(product) : true &&
                chkClient ? x.Client.Contains(client) : true &&
                chkProduct ? x.Product.Contains(product) : true &&
                chkProduct ? x.Product.Contains(product) : true &&
                chkProduct ? x.Product.Contains(product) : true &&
                chkProduct ? x.Product.Contains(product) : true &&
                chkProduct ? x.Product.Contains(product) : true
                ).ToList();
                return false;
            }

            #region Commands
            public void Apply()
            {
                var tmp = new List<LicenseRecordModel>(list);
                dialogResult = new List<LicenseRecordModel>(list);
                string message = "";
                if (chkProduct)
                {
                    dialogResult =tmp.Where(x => x.Product.Contains(product.ToUpper())).ToList();
                    tmp = dialogResult;
                }
                if (chkClient)
                {
                    dialogResult = tmp.Where(x => x.Client.Contains(client.ToUpper())).ToList();
                    tmp = dialogResult;
                }
                if (chkDateAfter)
                {
                    DateTime after;
                    if (chkDateBefore)
                    {
                        DateTime before;
                        if (DateTime.TryParse(dateAfter, out after))
                        {
                            if (DateTime.TryParse(dateBefore, out before))
                            {
                                dialogResult = tmp.Where(x => DateTime.ParseExact(x.CreationDate, "yyyy-MM-dd", null) <= after && DateTime.ParseExact(x.CreationDate, "yyyy-MM-dd", null) >= before).ToList(); ;
                                tmp = dialogResult;
                            }
                            else message += "'Date Before' is not a valid date (yyyy-mm-dd)";
                        }
                        else message += "'Date After' is not a valid date (yyyy-mm-dd)";
                    }
                    else if (DateTime.TryParse(dateAfter, out after))
                    {
                        dialogResult = tmp.Where(x => DateTime.ParseExact(x.CreationDate, "yyyy-MM-dd", null) >= after).ToList();
                        tmp = dialogResult;
                    }
                    else message += "'Date After' is not a valid date (yyyy-mm-dd)";
                }
                if (chkDateBefore)
                {
                    DateTime before;
                    if (DateTime.TryParse(dateBefore, out before))
                    {
                        dialogResult = tmp.Where(x => DateTime.ParseExact(x.CreationDate, "yyyy-MM-dd", null) <= before).ToList();
                        tmp = dialogResult;
                    }
                    else message += "'Date After' is not a valid date (yyyy-mm-dd)";
                }
                if (chkSbsNo)
                {
                    dialogResult = tmp.Where(x => x.SbsNo.Contains(sbsNo)).ToList();
                    tmp = dialogResult;
                }
                if (chkStoreNo)
                {
                    dialogResult = tmp.Where(x => x.StoreNo.Contains(storeNo)).ToList();
                    tmp = dialogResult;
                }
                if (chkWorkstationNo)
                {
                    dialogResult = tmp.Where(x => x.WorkstationNo.Contains(workstationNo)).ToList();
                    tmp = dialogResult;
                }
                if (chkComment)
                {
                    dialogResult = tmp.Where(x => x.Comment.ToUpper().Contains(comment.ToUpper())).ToList();
                    tmp = dialogResult;
                }
                if (message != "")
                {
                    MessageBox.Show(message);
                }
                else
                {
                    DialogCloser.SetDialogResult(wnd, true);
                }
            }
            #endregion
        }

EDIT:更新了视图,绑定设置为 TwoWay(已解决)

【问题讨论】:

  • 另外,如果使用静态类,您需要使用x:Static 进行绑定。
  • 绑定到vm属性,然后在类中我在静态类中设置值
  • 您正在设置所有tmp 字段,您不应该设置实际属性吗?看看public FilterWindowViewModel(IEnumerable&lt;LicenseRecordModel&gt; source) 构造函数......你真的应该在你设置的类中拥有所有道具。在设置器中,您也可以设置静态字段。您的绑定正在寻找这些属性。您是否偶然查看了输出中的任何绑定错误?
  • 绑定工作得很好,问题是当我第二次打开窗口时,我想取回相同的值,如果我按照你所说的在构造函数中设置它们,它会被覆盖为空调用 ShowDialog() 时的字符串
  • 你为什么要创建一个 WindowFilterWindowView 的对话框,但向我们展示 AddLicenseWindow 的 XAML?

标签: c# wpf mvvm dialog


【解决方案1】:

几个问题:

1) 由于您要将Helper(视图模型默认值)值发送到TextBox.Text,因此绑定必须至少为OneWay(源--> 目标)。因为您还想将输入数据从TextBox.Text 发送到视图模型,所以绑定变为TwoWay(源 目标)。

由于TwoWayTextBox.Text 属性的默认Binding.Mode,您可以安全地从绑定表达式中删除Binding.Mode

<TextBox Text="{Binding product, UpdateSourceTrigger=PropertyChanged}" />

2) 您不需要Helper 类来存储数据。只需向实例化对话框的类添加一个属性。
此外,您不再需要视图模型中的 RestoreCurrentFilters() 方法和一堆 tmp... 字段。

public partial class MainWindow
{
  // Shared and reused view model instance
  private FilterWindowViewModel DialogViewModel{ get; set; }

  public MainWindow()
  { 
    this.DialogViewModel = new FilterWindowViewModel();
  }

  private void ShowDialog()
  {
    var dialog = new FilterWindowView() { DataContext = this.DialogViewModel };
    if (dialog.ShowDialog() ?? false)
    {
      //...
    }
  }
}

FilterWindowViewModel.cs

public class FilterWindowViewModel 
{
  private string _product;
  public string Product
  {
    get => _product; 
    set
    {
      if (_product == value)
        return;

      _product = value;

      this.chkProduct = !string.IsNullOrWhiteSpace(value);
      OnPropertyChanged();
    }
  }
}

3) 您的视图模型不应引用Window
4) 更喜欢数据验证。只需实现INotifyDataErrorInfoExample

【讨论】:

  • 1) 这是我遗漏的明显部分......无论出于何种原因,我只是看不到它遗漏 2) RestoreCurrentFilters() 和 tmp 字段是解决问题的一种尝试,所以是的它们现在将消失 3)我发现调用对话框窗口的唯一方法是使用静态 DialogCloser 类,该类要求 VM 了解视图,之前要求不同的解决方案但没有找到任何解决方案。 4)不知道这个接口,会研究它
  • 所以您是从另一个视图模型显示对话框?或者为什么你认为静态 DialogCloser 是显示对话框的唯一方式?
  • 这是一个usage example 符合 MVVM 的可定制实现附加行为以显示来自视图模型的对话框。您可以通过NuGet 获取图书馆。它使用绑定到对话框视图模型的附加属性。设置此属性将立即显示对话框。通过定义以对话框视图模型类型为目标的DataTemplate,您可以自定义外观。
  • 由于您可以将异步回调委托传递给对话框视图模型,一旦对话框关闭,该委托就会自动调用,因此一切都是异步的,即发即弃。
【解决方案2】:

当您说“调用 ShowDialog() 时,值被重置为“”,这是因为 TextBox 绑定使用Mode=OneWayToSource。对文本框文本的更改将更新 VM 属性,但反之则不会。创建视图时,文本框不会填充您在构造函数中设置的 VM 属性值,因此它们将保持空白。

此外,这些文本框值(空字符串)实际上会在首次创建视图时更新回 VM 属性,从而产生您所看到的行为。从每个文本框中删除绑定模式,它应该可以正常工作。

【讨论】:

  • 完全乱七八糟的,你说得对,这就是缺少的东西,不知道我怎么这么久没看到它......
猜你喜欢
  • 2015-10-16
  • 2012-11-24
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 2014-01-29
  • 1970-01-01
相关资源
最近更新 更多