简体   繁体   中英

WPF datagrid: Finding a control when adding a row

So, basically I have a list of jobs that I'm keeping track of in a Datagrid. In that datagrid, I have a button I want to be a "Cancel" button when the job is running, but otherwise be a "Retry" button.

So, I've added the button to my grid:

<DataGridTemplateColumn x:Name="JobActionColumn" Header="">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Grid>
                <Button Click="JobActionButton_Click" Content="Resend" Name="JobActionButton" Height="18" Width="45" Margin="0,0,0,0" />
            </Grid> 
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

And in the code, I add my object in an ObservableCollection to add it to the grid:

    _jobs.Add(job);
    CollectionViewSource jobViewSource = this.FindViewSource("JobViewSource");
    jobViewSource.View.Refresh(); // Ensure that the new job appears at the top of the grid.
    JobDataGrid.SelectedItem = job;

    // Note: The Controller.Completed event handler disposes the controller object.
    Controller controller = new Controller(_historyContext);
    _controllers.Add(controller);
    controller.Completed += Controller_Completed;
    controller.Process(job);

    GetGridButton("JobActionButton", job).Content = "Cancel";

With GetGridButton being:

    private Button GetGridButton(string name, Job job)
    {            
        var selectedRow = (DataGridRow)JobDataGrid.ItemContainerGenerator.ContainerFromItem(job);

        return ExtensionMethods.FindVisualChildren<Button>(selectedRow).First(x => x.Name == name);            
    }

I've confirmed that GetGridButton works with rows that already exist. The problem is that when you add a new row to the underlying dataset and call this, it can't find the DataGridRow . I assume this is because it hasn't been created yet. So, looking through events, it looked like that the LoadingRow event would be a good candidate:

    private void JobDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
    {
        Job job = (Job)e.Row.Item;

        if (_controllers.FirstOrDefault(x => x.Job == job) != null)
        {
            var y = ExtensionMethods.FindVisualChildren<Button>(e.Row);
            Button button = ExtensionMethods.FindVisualChildren<Button>(e.Row).First(x => x.Name == "JobActionButton");
            button.Content = "Cancel";
        }
    }

So, now there is a DataGridRow object to pass into FindVisualChildren , but it still doesn't find any buttons. So, is there any way for me to access this button on an added row?

The preferred way to work with WPF is called MVVM .

This is my take on what you described:

<Window x:Class="MiscSamples.MVVMDataGrid"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MVVMDataGrid" Height="300" Width="300">
    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False"
              CanUserAddRows="False">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}"/>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <Grid>
                            <Button Command="{Binding CancelCommand}" Content="Resend" 
                                    Height="20" Width="45" Margin="0,0,0,0" x:Name="btn" />
                        </Grid>

                        <DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding IsRunning}" Value="True">
                                <Setter TargetName="btn" Property="Content" Value="Cancel"/>
                            </DataTrigger>
                        </DataTemplate.Triggers>

                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Window>

Code Behind:

public partial class MVVMDataGrid : Window
{
    public MVVMDataGrid()
    {
        InitializeComponent();

        DataContext = Enumerable.Range(1, 5)
                                .Select(x => new Job {Name = "Job" + x})
                                .ToList();
    }
}

Data Item:

public class Job: PropertyChangedBase
{
    public string Name { get; set; }

    private bool _isRunning;
    public bool IsRunning
    {
        get { return _isRunning; }
        set
        {
            _isRunning = value;
            OnPropertyChanged("IsRunning");
        }
    }

    public Command CancelCommand { get; set; }

    public Job()
    {
        CancelCommand = new Command(() => IsRunning = !IsRunning);
    }
}

PropertyChangedBase class (MVVM helper class):

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Command class (MVVM Helper class):

 //Dead-simple implementation of ICommand
    //Serves as an abstraction of Actions performed by the user via interaction with the UI (for instance, Button Click)
    public class Command : ICommand
    {
        public Action Action { get; set; }

        public void Execute(object parameter)
        {
            if (Action != null)
                Action();
        }

        public bool CanExecute(object parameter)
        {
            return IsEnabled;
        }

        private bool _isEnabled = true;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                _isEnabled = value;
                if (CanExecuteChanged != null)
                    CanExecuteChanged(this, EventArgs.Empty);
            }
        }

        public event EventHandler CanExecuteChanged;

        public Command(Action action)
        {
            Action = action;
        }
    }

Result:

在此处输入图片说明

  • Notice how I'm leveraging DataBinding in order to simplify the code and remove the need to find the element in the Visual Tree, and manipulating it in procedural code.
  • The logic is completely decoupled from the View, by the use of the Command that abstracts the button functionality.
  • Not a single line of code in the Code Behind. Only boilerplate generating the sample entries.
  • Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM