0

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?

jbirzer
  • 13
  • 5

1 Answers1

0

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:

enter image description here

  • 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.
Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154