2

So I am using WPF and the MVVM pattern. I have my datagrid's ItemSource property bound to a collection of domain objects in my ViewModel. The datagrid also has a RowDetailsTemplate defined which helps me modify all of my properties on my domain object. I have the datagrid set up so it only allows a single row to be selected at a time.

In case you are wondering here is what my datagrid declaration looks like:

<DataGrid Name="detailsDataGrid" Grid.Row="1" 
          AutoGenerateColumns="False"
          ItemsSource="{Binding Source={StaticResource Locator}, Path=Details.FileMoverDetails, Mode=OneWay, ValidatesOnDataErrors=True}" 
          SelectedItem="{Binding Source={StaticResource Locator}, Path=Details.SelectedDetail, Mode=TwoWay, ValidatesOnDataErrors=True}" 
          AreRowDetailsFrozen="True" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" SelectionMode="Single">

So you will also notice that the SelectedItem is also bound to a property in my ViewModel

Here is my problem: below my datagrid I have an 'add new record' button

<Button Name="newRecordBtn" DockPanel.Dock="Right" Margin="0,0,10,0" Click="newRecordBtn_Click" >New Record</Button>

which (inside the click event) executes my 'AddDetailCommand' in my ViewModel. The 'AddDetailCommand' simply creates a new domain object, adds it to the ViewModel's collection of domain objects and then sets the ViewModel's 'SelectedDetail' property to the newly created object. Then I call RaisePropertyChanged("FileMoverDetails") followed by RaisePropertyChanged("SelectedDetail").

This makes my datagrid select the newly created item and expand the datagrid's row details. My datagrid's row details has a lot of fields including ComboBoxes and TextBoxes. Which brings me to my real problem: when I left click once on one of my TextBoxes so that I can start typing it doesn't place focus in the text box and I have to click a second time before I can start typing. This happens with any control inside my row details though: I have to click once on any control and then click a second time before anything happens. This problem is only encountered if I click my 'add new record' button. Meaning if I select an already existing row in the datagrid then the focus is correct and I can click once on any row details control and it will work as expected.

For another example of the issue I am having, I have a 'remove record' button in the datagrid's row details which when activated removes the currently selected domain object from the datagrid's item collection. After clicking the 'Add new record' button I go and immediately click once on the 'remove record' button and nothing happens. But when I click a second time then the button works.

So what am I asking? After I click my 'add new record' button I want to be able to click once on my 'remove record' button and have its functionality activated without having to click another time.

.

.

Extra information:

I thought this was purely a focus issue so I ammended my 'add new record' button's Click event to set the focus to one of my datagrid's row details TextBox control after adding the new record:

    private void newRecordBtn_Click(object sender, RoutedEventArgs e)
    {
        if (e.Source == newRecordBtn)
        {
            ExecuteNewRecordButtonCommand();
            e.Handled = true;
        }
    }

    private void ExecuteNewRecordButtonCommand()
    {
        if (newRecordBtn.Command.CanExecute(null))
        {
            newRecordBtn.Command.Execute(null);

            if (detailsDataGrid.Items.Count > 0)
            {
                detailsDataGrid.UpdateLayout();

                DataGridRow selectedRow = (DataGridRow)(detailsDataGrid.ItemContainerGenerator.ContainerFromItem(detailsDataGrid.SelectedItem));


                //Get the ContentPresenter of the row details.
                DataGridDetailsPresenter presenter = FindVisualChild<DataGridDetailsPresenter>(selectedRow);

                // Find the localDirectoryText TextBox
                DataTemplate template = presenter.ContentTemplate;

                System.Windows.Controls.TextBox localDirTxt = (System.Windows.Controls.TextBox)template.FindName("localDirectoryText", presenter);

                localDirTxt.Focus();
            }

        }
    }

Guess what happened? It set the keyboard focus into the TextBox like I expected it would (I can start typing immediately and it will place my typed text into the TextBox). But when I click once on a different TextBox or my 'Remove record' button or a different control (all of which are in the datagrid's row details) nothing happens! I have to then click a second time for the control to do what I expected only a single click to do.

Please help, I have tried so hard to get this to work and it is very frustrating. Even if you can't help I want to thank you for getting to this point in this description, I know it is very long.

Here is my View's XAML (well at least the relevant parts, I remove stuff and placed "..." in the places where stuff was removed):

<Grid>
    <Grid.RowDefinitions>
    ...
    </Grid.RowDefinitions>
    <DataGrid Name="detailsDataGrid" Grid.Row="1" AutoGenerateColumns="False" ItemsSource="{Binding Source={StaticResource Locator}, Path=Details.FileMoverDetails, Mode=OneWay, ValidatesOnDataErrors=True}" SelectedItem="{Binding Source={StaticResource Locator}, Path=Details.SelectedDetail, Mode=TwoWay, ValidatesOnDataErrors=True}" AreRowDetailsFrozen="True" CanUserAddRows="False" CanUserDeleteRows="False" IsReadOnly="True" SelectionMode="Single">
        <DataGrid.Columns>
            ...
            <DataGridTextColumn Header="Local Directory" Binding="{Binding Path=LocalDirectory, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" Width="175"/>
            <DataGridTextColumn Header="Remote Directory" Binding="{Binding Path=RemoteDirectory, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" Width="175"/>
            ...
        </DataGrid.Columns>
        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <Grid Name="rowDetailsGrid" Margin="5,10,0,10" Width="{Binding ElementName=detailsView, Path=ActualWidth}"  HorizontalAlignment="Left" VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Locator}, Path=Details.SelectedDetail}" IsEnabled="{Binding Source={StaticResource Locator}, Path=Details.CanEditDetails, Mode=OneWay}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="190"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="250"/>
                        <ColumnDefinition Width="40"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <StackPanel Grid.Column="0" Grid.Row="8" Grid.ColumnSpan="3" Orientation="Vertical">
                        ...
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="1*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBox Grid.Column="0" Name="localDirectoryText" Text="{Binding Path=LocalDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" PreviewKeyDown="localDirectoryText_PreviewKeyDown"/>
                            <Button Grid.Column="1" Width="20" Margin="3,3,0,3" Name="localDirectoryButton" Click="localDirectoryButton_Click">...</Button>
                        </Grid>
                    </StackPanel>
                    <StackPanel Grid.Column="0" Grid.Row="9" Grid.ColumnSpan="3" Orientation="Vertical">
                        ...
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="1*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <TextBox Grid.Column="0" Name="remoteDirectoryText" Text="{Binding Path=RemoteDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" PreviewKeyDown="remoteDirectoryText_PreviewKeyDown"/>
                            <Button Grid.Column="1" Width="20" Margin="3,3,0,3" Name="remoteDirectoryButton" Click="remoteDirectoryButton_Click">...</Button>
                        </Grid>
                    </StackPanel>
                    ...
                    <DockPanel Grid.Column="4" Grid.Row="10" LastChildFill="False">
                        <Button Name="removeRecordBtn" DockPanel.Dock="Right" Margin="0,15,0,0" Command="{Binding Path=RemoveSelectedDetailCommand}">Remove Record</Button>
                    </DockPanel>
                </Grid>
            </DataTemplate>
        </DataGrid.RowDetailsTemplate>
    </DataGrid>
    <Grid Grid.Row="2" DataContext="{Binding Source={StaticResource Locator}, Path=Details}" IsEnabled="{Binding Path=HasInstance, Mode=OneWay}" Width="{Binding ElementName=detailsView, Path=ActualWidth}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="Auto" MinWidth="125"/>
        </Grid.ColumnDefinitions>
        <DockPanel Grid.Column="1" LastChildFill="False" Margin="0,10,0,7">
            <Button Name="newRecordBtn" DockPanel.Dock="Right" Margin="0,0,10,0" Command="{Binding Path=AddDetailCommand}" Click="newRecordBtn_Click" >New Record</Button>
        </DockPanel>
    </Grid>
</Grid>
4

1 回答 1

2

经过许多谷歌浏览器标签和耐心之后,我终于能够解决我的问题。

我不知道为什么我以前的 ExecuteNewRecord() 方法没有获得正确的焦点,但我通过以下方式修复了它:首先将焦点放在当前选定行中的第一个单元格上,然后将焦点移到第二个单元格上在当前选定的行中。这样做以某种方式纠正了焦点问题。

这是我修改后的 newRecordBtn_Click() 和 ExecuteNewRecordButtonCommand():

private void newRecordBtn_Click(object sender, RoutedEventArgs e)
{
    if (e.Source == newRecordBtn)
    {
        ExecuteNewRecordButtonCommand();
        e.Handled = true;
    }
}

private void ExecuteNewRecordButtonCommand()
{
    if (newRecordBtn.Command.CanExecute(null))
    {
        newRecordBtn.Command.Execute(null);

        if (detailsDataGrid.Items.Count > 0)
        {
            detailsDataGrid.UpdateLayout();

            DataGridRow selectedRow = 
                (DataGridRow)(detailsDataGrid.ItemContainerGenerator
                                                .ContainerFromItem(detailsDataGrid.SelectedItem));

            DataGridCellsPresenter cellPresenter = FindVisualChild<DataGridCellsPresenter>(selectedRow);

            System.Windows.Controls.DataGridCell firstCell =
                (System.Windows.Controls.DataGridCell)(cellPresenter.ItemContainerGenerator
                                                                    .ContainerFromIndex(0));

            firstCell.Focus();
            firstCell.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }
    }
}

另外,如果有人想知道 FindVindVisualChild<>() 方法是什么,请注意我不是该方法的原始作者,我是从http://msdn.microsoft.com/en-us/library/获得的bb613579.aspx

于 2014-08-14T18:34:16.887 回答