防止在更改 itemsource 时触发 datagrid 的复选框事件

Prevent checkbox events of datagrid being triggered when itemsource has been changed

提问人:Prouder 提问时间:4/22/2023 最后编辑:Mark RotteveelProuder 更新时间:5/25/2023 访问量:83

问:

我有一个包含各种单元格的数据网格(一个单元格是一个复选框)。有一个复选框事件(选中和未选中),我用它来向全局余额帐户(文本框)添加 x 金额。数据仅在一年内有效,因此,如果我更改当前年份(通过组合框选择)以评估或检查某些内容,则会更改数据网格的数据源()。ObservableCollection<Member>

我希望当我加载新数据源时,只交换数据网格中的数据。

实际情况是,当我将新数据源(不同的列表)加载到数据网格中时,所有选中和未选中的事件都会被调用,因此会更改文本框的平衡值。

如何防止调用这些事件。是否有类似最后一个事件的东西,我可以用它来设置一个标志(“newSourceInitialized”)来启用检查事件,以便它们仅在用户实际单击它们时才会触发?

WPF 代码位于此处:

<Grid x:Name="MainGrid">
    <Grid x:Name="Navbar" Background="#FFADECED" Height="30" VerticalAlignment="Top">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <!--A column for every item in navbar grid must be added here-->
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Content="Aktuelles Jahr" VerticalAlignment="Center" HorizontalAlignment="left" />
        <ComboBox Grid.Column="1" Name="cbxYear" VerticalAlignment="Center" HorizontalAlignment="left" SelectionChanged="cbxYearSelectionChanged" ContextMenuClosing="cbxLostFocus" />
        <Button Grid.Column="2" Name="btnAddNewYear" BorderBrush="Transparent" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="left">
            <StackPanel Orientation="Horizontal">
                <Image Margin="5,0,0,0" Source="/data/images/add-24-blue.png" Stretch="None"/>
                <TextBlock Margin="5,0,10,0" VerticalAlignment="Center"><Run Text="Neues Jahr"/></TextBlock>
            </StackPanel>
        </Button>
        <Button Grid.Column="3"  Name="btnSave" Background="Transparent" BorderBrush="Transparent" Click="btnSave_Click" VerticalAlignment="Center" HorizontalAlignment="left">
            <DynamicResource ResourceKey="unsaved"/>
        </Button>
        <TextBox Grid.Column="4" Name="tbxBalance" TextChanged="textBoxNumeric_TextChanged" TextAlignment="Right" TextWrapping="NoWrap" Text="TextBox" VerticalAlignment="Center" HorizontalAlignment="left" Width="151"/>

    </Grid>
    <Grid x:Name="MainPane">
        <!-- loaded event necessary cause otherwise checked events will be triggered -> changing the balance -->
        <DataGrid Name="dgMembers" d:ItemsSource="{d:SampleData ItemCount=5}" AutoGenerateColumns="False" Margin="0,27,0,0" Loaded="gridLoaded">
            <DataGrid.Resources>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="Background" Value="#FF96BFF5"/>
                    <Setter Property="FontWeight" Value="SemiBold"/>
                    <Setter Property="BorderThickness" Value="0,0,1,2"/>
                    <Setter Property="BorderBrush" Value="Black"/>
                </Style>
            </DataGrid.Resources>

            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding Path=id}"/>
                <DataGridTextColumn Header="Vorname" Binding="{Binding Path=firstname}"/>
                <DataGridTextColumn Header="Nachname" Binding="{Binding Path=lastname}"/>
                <DataGridTextColumn Header="Geburtstag" Binding="{Binding Path=birthday, StringFormat={}\{0:dd.MM.yyyy\}}"/>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <CheckBox Name="cbxPayed" IsChecked="{Binding Path=payed}" Checked="cbxPayedChecked" Unchecked="cbxPayedUnchecked" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Bemerkung" Binding="{Binding Path=note}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Grid>

C# 代码

public partial class MainWindow : Window {
    ObservableCollection<Member> lstMembers;
    private int MemberDue;
    private bool isInitialized = false;
    private bool hasChangesToBeSaved = true;//TODO start with false
    public MainWindow() {
        InitializeComponent();
        initializeData();
    }

    private void btnSave_Click(object sender, RoutedEventArgs e) {
        if (hasChangesToBeSaved) { btnSave.Content = FindResource(btnSave.Content == FindResource("unsaved") ? "saved" : "unsaved"); }
        saveEverything();
    }
    private async Task saveEverything() {
        await io.saveMembers(lstMembers, "2023");// io is a class for json reading and writing
        Properties.Settings.Default.Save();
        hasChangesToBeSaved = false;
    }

    private void initializeData() {
        cbxYear.ItemsSource = io.getAllFiles();
        cbxYear.SelectedIndex = 0;
        loadData();
    }

    private void loadData() {
        lstMembers = io.readMembers(cbxYear.SelectedValue.ToString());
        dgMembers.ItemsSource = lstMembers;
        MemberDue = Convert.ToInt32(Properties.Settings.Default.MemberDue);
        tbxBalance.Text += Properties.Settings.Default.Balance;
        isInitialized = true;
    }

    private void gridLoaded(object sender, EventArgs e) {
        isInitialized = true;
    }

    private void cbxYearSelectionChanged(object sender, EventArgs e) {
        if (isInitialized) {
            isInitialized = false; // prevent check events being executed while data is loading

            loadData();
        }
    }
    
    private void cbxLostFocus(object sender, EventArgs e) {
        isInitialized = true;
    }

    private void cbxPayedUnchecked(object sender, EventArgs e) {
        if (isInitialized) {
            string balance = tbxBalance.Text.Equals(String.Empty) ? "0" : tbxBalance.Text;
            tbxBalance.Text = (Convert.ToDouble(balance) - MemberDue).ToString();
        }
    }
    
    private void cbxPayedChecked(object sender, EventArgs e) {
        if (isInitialized) {
            string balance = tbxBalance.Text.Equals(String.Empty) ? "0" : tbxBalance.Text;
            tbxBalance.Text = (Convert.ToDouble(balance) + MemberDue).ToString();
        }
    }
}

现在,我在启动时阻止检查事件,并使用数据网格的“loaded”事件重新激活它们。这适用于启动。现在,对于用于加载不同年份的数据源更改函数,我想要相同的内容。

C# WPF 事件 复选框 DataGrid

评论

0赞 BionicCode 4/23/2023
您介意更详细地了解您的期望和正在发生的事情吗?目前尚不清楚发生了什么。通常,更改数据源实例不会导致引发 UI 事件。例如,根据 IsChecked 值勾选复选框,但这不会引发 Checked 事件。这是因为当您更改源时,将创建新的项容器。在依赖项对象的初始化例程期间,将新值分配给依赖项属性。在此例程中,不会发生任何事件。
0赞 BionicCode 4/23/2023
此外,还发布一个重现您的问题的最小示例。这有助于了解您在做什么。
0赞 Prouder 4/24/2023
我更新了问题。希望这是对我遇到的问题的更好描述。
0赞 mm8 4/25/2023
@Prouder:您是否尝试在设置 之前将标志设置为,即在方法的第一行?isInitializedfalseItemsSourceloadData
0赞 Prouder 4/26/2023
@mm8是的,我做到了。我在“cbxYearSelectionChanged”函数中执行此操作,该函数再次调用“loadData”函数以更改当前年份。我调试了它,并且在数据源更改后调用检查事件,从而更改了之后的计算。

答:

0赞 Prouder 5/24/2023 #1

解决方案既简单又令人困惑。我在加载新源之前设置了“isInitialized = false”,并在更新源时设置了“isInitialized = true”。

我还更改了“AutoGenerateColumns=”False“并删除了 d:ItemsSource=”{d:SampleData ItemCount=5}”。 这有点帮助:DataGrid 捕获单元格值更改事件,只需单击一下 UpdateSourceTrigger = SourceUpdated

现在,当我加载一个新源时,平衡文本框每次重新加载都会得到另一个“0”,尽管该值实际上是“0”,但这是一个与此无关的不同主题。