C# 更改DataGrid时,添加带有MessageBox的复选框

C# 更改DataGrid时,添加带有MessageBox的复选框,c#,wpf,mvvm,C#,Wpf,Mvvm,我已经发布了一个桌面应用程序(因此我希望有一个能将更改和回归测试保持在最低限度的答案),当网格更改时,我需要添加一致性检查CanBeDeleted <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding CurrentPosIn.PosInLocationsList}" CanUserAddRows="{Binding UpdateEnabled}" CanUserDeleteRows="{Binding

我已经发布了一个桌面应用程序(因此我希望有一个能将更改和回归测试保持在最低限度的答案),当网格更改时,我需要添加一致性检查
CanBeDeleted

<DataGrid AutoGenerateColumns="False" 
    ItemsSource="{Binding CurrentPosIn.PosInLocationsList}" 
    CanUserAddRows="{Binding UpdateEnabled}" CanUserDeleteRows="{Binding UpdateEnabled}" >
我在这里添加了一致性检查

    string _storage;
    [Column(Name = "storage"), PrimaryKey]
    public string Storage {
        get { return _storage; }
        set { 
            if (this.loadedEF) {
                string validate_msg;
                if (!PurchasePosIn.CanBeDeleted(out validate_msg)) {
                    // against MVVM
                    MessageBox.Show(validate_msg, "Alert", MessageBoxButton.OK); 
                    OnPropertyChanged( () => Storage  );
                    return;
                }
                Persistence.MyContext.deletePosInLocation(this);
            }
            _storage = value;
            OnPropertyChanged( () => Storage  );
            if (this.loadedEF) {
                Persistence.MyContext.insertPosInLocation(this);
            }               
        }
    }
这里(第二部分)

也许它很难看(真的,它没那么难看:嗯,它很好,也适用于现代的
对话框
),但现在我正在设置

if (!CanBeDeleted(out validate_msg)) {
    PurchaseItem.MessageBoxText = validate_msg;
不可见的
文本框

    <TextBox Visibility="Hidden" Name="tb_message"
        Text="{Binding MessageBoxText}"
        TextChanged="TextBox_TextChanged"
回滚添加/删除的项目 我看到了与另一个问题的联系,在我的例子中,我认为防止删除更为重要。关于这个话题,我不知道是否有比这个(出现的)更多的更新答案

虽然
PropertyChanged
可以很容易地引发以回滚项更新,但是对于集合更改,我被迫在
CollectionChanged
事件中传递并引用视图调度程序

PurchaseItem.dispatcher.BeginInvoke((Action)(() => RollBack(args)));
回滚添加/删除的项目的步骤

    bool rollingBack = false;
    private void RollBack(NotifyCollectionChangedEventArgs args) {
            rollingBack = true;
            if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (var element in args.OldItems) {
                    PosInLocationsList.Add((PosInLocation)element);
                }
            }
            if (args.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (var element in args.NewItems) {
                    PosInLocationsList.Remove((PosInLocation)element);
                }
            }
            rollingBack = false;
    }

如果ObservableCollection实现了“previewCollectionChanged”,事情就会简单得多。

出于您的需要,我建议创建ObservableCollection的子类并重载受保护的方法RemoveItem。
根据您对应用程序的操作,您可能希望覆盖除RemoveItem之外的其他方法(如ClearItems)。

当对ObservableCollection进行子类化时,有5种受保护的方法可以覆盖:ClearItems、RemoveItem、InsertItem、SetItem和MoveItem
这些方法最终会被所有公共方法调用,因此重写它们可以让您完全控制。

下面是一个可以运行的小应用程序,演示了这一点:

可观测集合子类

XAML 应用程序主 我已经在启动时设置了ViewModel的窗口,但您可能应该在创建ViewModel的任何位置执行此操作

我现在所做的是

针对MVVM,因为我已经在模型中添加了警报

@Tesseract、子类化
observeCollection
和订阅
removietem
的解决方案已经面向MVVM

仍然缺少的是从ViewModel发送警报的正确、现代的方法。这是有帮助的地方

  • 在窗口中使用附加属性注册视图模型 使用对话框子系统
在您的XAML中

Dialog:DialogParticipation.Register="{Binding}"
其中附加属性将通过字典跟踪视图

public static class DialogParticipation
{
    private static readonly IDictionary<object, DependencyObject> ContextRegistrationIndex = new Dictionary<object, DependencyObject
通过上述附加属性(视图模型为
上下文


并显示对话框,调用检索到的视图上的相应方法:如果打开多个窗口,对话框将显示在正确的窗口上。

@user1892538我用一个示例更新了我的帖子。这适用于您的应用程序吗?我对您的答案投了赞成票,但我想强调两点:1)为了使它更可重用,除了DeletionDenied事件之外,我还应该定义一个CanDelete(类似地,其他2个用于insert),这样这个类就不负责条件逻辑。2) “我还没有找到一个在mvvm中显示弹出窗口的好方法和短方法。”虽然我接受你关于子类化和事件的回答,但这部分内容太粗糙了,我同意理想的解决方案(依赖属性)不是那么简单和短。。。但是为什么你不喜欢我答案第一部分的方法呢?坦率地说,我会坚持下去。@user1892538 1)我在我的课堂上定义了一个烛台,或者你还有别的想法吗?2) 啊,我不知道MessageBox类是一个标准类。是的,使用这个比我做的要简单得多(我稍后会尝试更新我的答案),但基本上是一样的。我不喜欢这个解决方案的原因是因为它打破了MVVM模式,ViewModel必须知道Window(或者在您的案例中是MessageBox)类才能运行,它应该只属于视图。一个更干净的方法可能是在VM和用户信息逻辑之间使用一个接口!关于第2点)我不是建议像我在问题中那样使用
MessageBox
,而是一个标准的mvvm字符串属性,绑定到
TextBox
(或者更优雅的dp),触发xaml中的
MessageBox
:我认为从mvvm的角度来看,这是完全正确的,不是吗。。。顺便说一句,您的
INotifyPropertyChanged
的实现是错误的(我知道这是不相关的,但最好不要显示它)最后一点1)也许您是对的(我想到了另一个事件处理程序),我会再次检查这一点point@user1892538好的,我理解你对留言框的建议,是的,我认为这是一个理想的解决办法。我还没有玩太多的独立歌剧,我会在未来试一试。“您对INotifyPropertyChanged的实现是错误的”,您是指我忘记检查它是否为空的事实吗?谢谢你指出,我会更新的。
    bool rollingBack = false;
    private void RollBack(NotifyCollectionChangedEventArgs args) {
            rollingBack = true;
            if (args.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (var element in args.OldItems) {
                    PosInLocationsList.Add((PosInLocation)element);
                }
            }
            if (args.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (var element in args.NewItems) {
                    PosInLocationsList.Remove((PosInLocation)element);
                }
            }
            rollingBack = false;
    }
public class ObservableCollectionWithDeletionControl<T> : ObservableCollection<T>
{
    public delegate void DeletionDeniedEventHandler(object sender, int indexOfDeniedDeletion);
    public event DeletionDeniedEventHandler DeletionDenied;

    public bool CanDelete { get; set; }

    protected virtual void OnDeletionDenied(int indexOfDeniedDeletion)
    {
        if (DeletionDenied != null) { DeletionDenied(this, indexOfDeniedDeletion); }
    }

    protected override void RemoveItem(int index)
    {
        if (CanDelete)
        {
            base.RemoveItem(index);
        }
        else
        {
            OnDeletionDenied(index);
        }
    }
}
public class MainWindowViewModel
{
    public MainWindow window { get; set; }

    public ObservableCollectionWithDeletionControl<Person> People { get; set; } = new ObservableCollectionWithDeletionControl<Person>();

    public MainWindowViewModel()
    {
        People.DeletionDenied += People_DeletionDenied;
    }

    private void People_DeletionDenied(object sender, int indexOfDeniedDeletion)
    {
        Person personSavedFromDeletion = People[indexOfDeniedDeletion];
        window.displayDeniedDeletion(personSavedFromDeletion.Name);
    }
}
public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
        get { return _name; }
        set
        {
            if(_name == value) { return; }
            _name = value;
            if( PropertyChanged != null ) { PropertyChanged(this, new PropertyChangedEventArgs("Name")); }
        }
    }

    private string _name = "";
}
<Window x:Class="WpfApplication1.MainWindow"
    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:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <CheckBox DockPanel.Dock="Top" Content="Can delete" IsChecked="{Binding People.CanDelete}" Margin="5" HorizontalAlignment="Left"/>
        <DataGrid ItemsSource="{Binding People}" Margin="5,0"/>
    </DockPanel>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public void displayDeniedDeletion(string name)
    {
        TextBox errorMessage = new TextBox();
        errorMessage.Text = string.Format("Cannot delete {0} : access denied !", name);

        Window popupErrorMessage = new Window();
        popupErrorMessage.Content = errorMessage;

        popupErrorMessage.ShowDialog();
    }
}
public partial class App : Application
{
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        MainWindow window = new MainWindow();
        MainWindowViewModel viewModel = new MainWindowViewModel();

        viewModel.window = window;
        window.DataContext = viewModel;
        window.Show();

        App.Current.MainWindow = window;
    }
}
Dialog:DialogParticipation.Register="{Binding}"
public static class DialogParticipation
{
    private static readonly IDictionary<object, DependencyObject> ContextRegistrationIndex = new Dictionary<object, DependencyObject
public class DialogCoordinator : IDialogCoordinator
{
    public static readonly IDialogCoordinator Instance = new DialogCoordinator();
var association = DialogParticipation.GetAssociation(context);