WPF应用程序MVVM - 在Textbox上使用OneWay绑定,在再次获取数据后,Property变为null

时间:2016-04-15 08:40:51

标签: c# wpf mvvm

我有一个使用MVVM的WPF应用程序。

我有一个绑定到ObservableCollection的DataGrid和一个绑定到DataGrid SelectedItem的TextBox,所以当我单击DataGrid中的一个项目时,将填充TextBox。

我还有一个使用Command和CommandParameter的Button,并使用RelayCommand检查TextBox是否为空,然后禁用Button。

如果我使用UpdateSourceTrigger = PropertyChanged,那一切都很好用。我不喜欢的是因为绑定,如果用户更改TextBox中的文本,则编辑DataGrid记录。如果用户随后改变了关于更改记录的想法,并在其他位置单击,则DataGrid中的记录仍会显示已编辑的文本。

我试过在TextBox绑定上使用Mode = OneWay,它的工作原理是它不会更新DataGrid记录。将数据保存到数据库后,我需要手动刷新DataGrid以显示更改。

我后面代码中的代码是DataGrid的SelectionChanged事件,它将ViewModel上的属性设置为所选项目。

因此,为了显示新的更改,我想在更改生效后再次调用我的GetCategories。但是,当代码执行OnPropertyChanged(“ReceivedCategories”)时,我的CurrentCategory属性变为null。

我的代码:

CategoryModel.cs

public class CategoryModel
{
    public int CategoryID { get; set; }
    public string Description { get; set; }

    readonly SalesLinkerDataContext _dbContext = new SalesLinkerDataContext();

    public ObservableCollection<CategoryModel> GetCategories() 
    {
       var result = _dbContext.tblSalesCategories.ToList();

       List<CategoryModel> categoriesList = result.Select(item => new CategoryModel
       {
           CategoryID = item.CategoryID, 
           Description = item.Description.Trim()
       }).ToList();


        return new ObservableCollection<CategoryModel>(categoriesList);
    }

    internal bool UpdateCategory(int id, string description)
    {
        if (_dbContext.tblSalesCategories.Any(x => x.Description == description))
        {
            MessageBox.Show("A category with the same name already exists.");
            return false;
        }

        try
        {
            var category = (from a in _dbContext.tblSalesCategories
                where a.CategoryID == id
                select a).FirstOrDefault();


            if (category != null)
            {
                category.Description = description;
                _dbContext.SubmitChanges();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            return false;
        }

        return true;
    }

    internal bool AddCategory(string description)
    {
        if (_dbContext.tblSalesCategories.Any(x => x.Description == description))
        {
            MessageBox.Show("A category with the same name already exists.");
            return false;
        }

        var newCategory = new tblSalesCategory();
        newCategory.Description = description;

        try
        {
            _dbContext.tblSalesCategories.InsertOnSubmit(newCategory);
            _dbContext.SubmitChanges();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            return false;
        }

        return true;



    }

    internal bool DeleteCategory(int id)
    {
        var result = _dbContext.tblSalesCategories.FirstOrDefault(x => x.CategoryID == id);

        try
        {
            if (result != null)
            {
                _dbContext.tblSalesCategories.DeleteOnSubmit(result);
                _dbContext.SubmitChanges();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            return false;
        }

        return true;
    }
}

CategoriesViewModel.cs

public class CategoriesViewModel : ViewModelBase, IPageViewModel
{        
    public CategoryModel CurrentCategory = new CategoryModel();

    public ObservableCollection<CategoryModel> Categories = new ObservableCollection<CategoryModel>(); 

    public RelayCommand GetCategoriesRelay;
    public RelayCommand UpdateCategoryRelay;
    public RelayCommand AddCategoryRelay;
    public RelayCommand DeleteCategoryRelay;

    #region Get Categories Command
    public ICommand GetCategoriesCommand
    {
        get
        {
            GetCategoriesRelay = new RelayCommand(p => GetCategories(),
            p => CanGetCategories());

            return GetCategoriesRelay;
        }
    }

    private bool CanGetCategories()
    {
        return true;
    }

    private void GetCategories()
    {
        Categories = CurrentCategory.GetCategories();
        ReceivedCategories = Categories;
    }
    #endregion

    #region Update Category Command
    public ICommand UpdateCategoryCommand
    {
        get
        {
            UpdateCategoryRelay = new RelayCommand(p => UpdateCategory((string) p),
                p => CanUpdateCategory());

            return UpdateCategoryRelay;
        }
    }

    public bool CanUpdateCategory()
    {
        return !String.IsNullOrWhiteSpace(Description);
    }

    public void UpdateCategory(string description)
    {
        if (CurrentCategory.UpdateCategory(CurrentCategory.CategoryID, description))
        {
            GetCategories();
        }
    }
    #endregion

    #region Add Category Command
    public ICommand AddCategoryCommand
    {
        get
        {
            AddCategoryRelay = new RelayCommand(p => AddCategory((string) p),
                p => CanAddCategory());

            return AddCategoryRelay;
        }
    }


    private bool CanAddCategory()
    {
        return !String.IsNullOrWhiteSpace(Description);
    }

    private void AddCategory(string description)
    {
        if (CurrentCategory.AddCategory(description))
            GetCategories();
    }

    #endregion

    #region Delete Category Command

    public ICommand DeleteCategoryCommand
    {
        get
        {
            DeleteCategoryRelay = new RelayCommand(p => DeleteCategory((int) p),
                p => CanDeleteCategory());

            return DeleteCategoryRelay;
        }
    }

    private bool CanDeleteCategory()
    {
        return true;
    }

    private void DeleteCategory(int id)
    {
        if (CurrentCategory.DeleteCategory(id))
            GetCategories();
    }

    #endregion



    /// <summary>
    /// Describes the name that will be used for the menu option
    /// </summary>
    public string Name
    {
        get { return "Manage Categories"; }  

    }

    public string Description
    {
        get
        { 
            return CurrentCategory.Description;      
        }
        set
        {
            CurrentCategory.Description = value;
            OnPropertyChanged("Description");
        }
    }


    public ObservableCollection<CategoryModel> ReceivedCategories
    {
        get { return Categories; }

        set
        {
            Categories = value;
            OnPropertyChanged("ReceivedCategories");
        }
    }
}

CategoryView.xaml

<UserControl x:Class="SalesLinker.CategoriesView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"      
  mc:Ignorable="d" 
  d:DesignHeight="300" d:DesignWidth="600" Background="White">

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding GetCategoriesCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid >
    <Grid.RowDefinitions>
        <RowDefinition Height="45"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250"/>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Label Grid.Row="0" Grid.Column="0" Margin="20,0,0,0" FontSize="20" HorizontalAlignment="Center" Content="Categories"/>

    <DataGrid x:Name="LstCategories" Grid.Column="0" Grid.Row="1"  AutoGenerateColumns="false"  

              ItemsSource="{Binding Path=ReceivedCategories, Mode=TwoWay}" SelectionChanged="Selector_OnSelectionChanged"
              HorizontalScrollBarVisibility="Disabled" GridLinesVisibility="None"
              CanUserAddRows="False" CanUserDeleteRows="False" CanUserSortColumns="True" Background="White">     
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding  Path=Description}" IsReadOnly="True" Header="Description" Width="300" />
        </DataGrid.Columns>
    </DataGrid>

    <Button Command="{Binding AddCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,20,0,0" Background="Transparent" BorderThickness="0" BorderBrush="Transparent"
            CommandParameter="{Binding ElementName=TbDescription, Path=Text}">
        <Image Source="/Images/Plus.png"/>
    </Button>

    <Button Command="{Binding DeleteCategoryCommand}" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Height="50" Width="50" Margin="0,75,0,0" Background="Transparent"  BorderThickness="0" BorderBrush="Transparent"
              CommandParameter="{Binding SelectedItem.CategoryID, ElementName=LstCategories, Mode=OneWay }">
        <Image Source="/Images/Minus.png"/>
    </Button>


    <Grid Grid.Row="1" Grid.Column="2">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="75"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Label VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" Content="Description:"/>

        <TextBox x:Name="TbDescription" DataContext="CategoryModel" Grid.Row="0" 
                 Grid.Column="1" Width="250" Height="Auto" VerticalAlignment="Center" 
                 HorizontalAlignment="Left" Margin="10,0,0,0"
                 Text="{Binding SelectedItem.Description, ElementName=LstCategories, Mode=OneWay}"/>

        <Button   Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Margin="10,0,0,0" 
                  Height="20" Width="120" Content="Update Description" 

                  Command="{Binding UpdateCategoryCommand}"
                  CommandParameter="{Binding ElementName=TbDescription, Path=Text}" />
    </Grid>
</Grid>

我还注意到在TextBox上使用Mode = OneWay也会破坏我的CanExecute代码片段。

所以我能想到的只有:

  1. 找到将另一个属性绑定到TextBox的方法吗?
  2. 在TextBox上找到一种使用UpdateSourceTrigger = PropertyChanged的方法,但是阻止更新DataGrid。
  3. 有什么想法吗?

2 个答案:

答案 0 :(得分:0)

对于Observable List中的null值,您必须使用它的副本和Dispatcher.Invoke更新列表,否则gui将崩溃或显示null我在更新线程中的可观察列表时遇到了类似的问题。 / p>

因此,您应该在以下列表中编写更改:

//Your Copy List
ObservableCollection<CategoryModel> _ReceivedCategories;
//Your Execute command for the Gui
public void onUpdateExecuted(object parameter){
Dispatcher.Invoke(new Action(() => ReceivedCategories = new ObservableCollection <CategoryModel> (_ReceivedCategories));
}

//Your Undo Command if the Inputs are not ok
public void onUndoExecuted(object parameter){
Dispatcher.Invoke(new Action(() => _ReceivedCategories = new ObservableCollection <CategoryModel> (ReceivedCategories));
}

//Your Command on every input which is set
public void onInputExecuted(object parameter){
_ReceivedCategories.add(Your Object);
}

并且您可以为主列表添加更新命令,因此您首先使用值更新复制列表,并在命令设置主集合中的复制集合之后

希望有所帮助

答案 1 :(得分:-1)

我无法通过SeeuD1获得上述建议,但已经弄明白问题是什么。

我将DataGrid的ItemsSource绑定到ReceivedCategories,并在后面的代码中使用SelectionChanged事件绑定到TextBox。

数据库保存后,我再次调用我的GetCategories()来刷新数据。

因此,当ReceivedCategories再次获取数据时,DataGrid的ItemsSource会更新,但不会在SelectionChange事件触发尝试将CurrentCategory设置为SelectedItem之前更新。但是,DataGrid中不再选择任何内容(索引为-1),因此赋值失败并将CurrentCategory设置为null。

修复只是在选择了某些内容时将CurrentCategory分配给SelectedItem。

旧代码:

private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var viewmodel = (CategoriesViewModel)DataContext;
        viewmodel.CurrentCategory = LstCategories.SelectedItems.Cast<CategoryModel>().FirstOrDefault();
    }

修正:

private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var viewmodel = (CategoriesViewModel)DataContext;

        if (LstCategories.SelectedIndex > -1)
            viewmodel.CurrentCategory = LstCategories.SelectedItems.Cast<CategoryModel>().FirstOrDefault();
    }

感谢您的建议。