Tony Rush
Tony Rush

Reputation: 329

DataTemplate to show Buttons in WPF ListView while maintaing Properties

In WPF, I have a ListView of 2 columns and the first column needs to be a button. Correct me if I'm wrong, but the only way I found to implement a button in a ListView is to use a DataTemplate. The problem I found with this is I have no way to maintain my original button Properties when they are mapped with a DataTemplate so I am forced to use binding to remap every individual property (including custom Properties since I'm actually using a custom User Control which inherits from Button). This seems extraneous to have to manually map all Properties so maybe there's a better way to automatically persist those properties?

Here's my test code:

public MainWindow() {
    InitializeComponent();

    ObservableCollection<ScreenRequest> screenRequests = new ObservableCollection<ScreenRequest>() {
        new ScreenRequest("A", "1"),
        new ScreenRequest("B", "2")
    };
    myListView.ItemsSource = screenRequests;
}   

public class ScreenRequest {
    public CustomButton ScreenButton { set; get; }
    public string Details { set; get; }

    public ScreenRequest(string buttonText, string customProperty) {
        this.ScreenButton = new CustomButton();
        this.ScreenButton.Content = buttonText;
        this.ScreenButton.CustomProperty = customProperty;
        this.ScreenButton.Click += new RoutedEventHandler(InitiateScreenRequest);
    }

    private void InitiateScreenRequest(object sender, RoutedEventArgs e) {
        CustomButton screenBtn = (CustomButton)sender;
        screenBtn.Content = "BUTTON TEXT CHANGED";
    }
}   

public class CustomButton : Button  {
    public string CustomProperty { get; set; }
}

And the XAML:

<Window...
...
    <Window.Resources>
        <DataTemplate x:Key="ButtonTemplate">
            <local:CustomButton Content="{Binding ScreenButton.Content}"/>
        </DataTemplate>
    </Window.Resources>
    <Grid x:Name="grdMain">
    ...
        <ListView...
            <ListView.View>
                <GridView x:Name="gridView">
                    <GridViewColumn CellTemplate="{StaticResource ButtonTemplate}" Width="Auto" Header="Screen" HeaderStringFormat="Screen"/>
                    <GridViewColumn Header="Details" HeaderStringFormat="Details" DisplayMemberBinding="{Binding Details}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

So my questions are:

  1. Do I have to manually map every single property in the CustomButton in order for it to carry over to the DataTemplate or is their a catch-all to automatically persist the Properties?
  2. How do I map the CustomProperty Property in the binding such that it sticks with the button? Do I use a DependencyProperty for this?
  3. How do I maintain my click event such that clicking the button in GridView will call the InitiateScreenRequest function? Ideally I'd like to have a single method declared for all buttons, but I haven't gotten to that point yet.

Any help or insight into buttons in listviews would be appreciated.

Upvotes: 0

Views: 5403

Answers (1)

Fede
Fede

Reputation: 44038

<Window x:Class="MiscSamples.TonyRush"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TonyRush" Height="300" Width="300">
    <ListView ItemsSource="{Binding}">
        <ListView.View>
            <GridView>
                <GridViewColumn Width="Auto" Header="Screen" HeaderStringFormat="Screen">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <Button Command="{Binding SomeAction}" Content="{Binding ActionDescription}" Width="100"/>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
                <GridViewColumn Header="Details" HeaderStringFormat="Details" DisplayMemberBinding="{Binding Details}" Width="100"/>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

Code Behind:

 public partial class TonyRush : Window
    {
        public TonyRush()
        {
            InitializeComponent();
            DataContext = new List<ScreenRequest>
                              {
                                  new ScreenRequest() {ActionDescription = "Click Me!"},
                                  new ScreenRequest() {ActionDescription = "Click Me Too!"},
                                  new ScreenRequest() {ActionDescription = "Click Me Again!!"},
                              };
        }
    }

ViewModel:

public class ScreenRequest: INotifyPropertyChanged
    {
        public Command SomeAction { get; set; }

        private string _actionDescription;
        public string ActionDescription
        {
            get { return _actionDescription; }
            set
            {
                _actionDescription = value;
                NotifyPropertyChanged("ActionDescription");
            }
        }

        private string _details;
        public string Details
        {
            get { return _details; }
            set
            {
                _details = value;
                NotifyPropertyChanged("Details");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public ScreenRequest()
        {
            SomeAction = new Command(ExecuteSomeAction) {IsEnabled = true};
        }

        //public SomeProperty YourProperty { get; set; }

        private void ExecuteSomeAction()
        {
            //Place your custom logic here based on YourProperty
            ActionDescription = "Clicked!!";
            Details = "Some Details";
        }
    }

Key part: The Command 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;
        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

Notes:

Take a look at how separate UI is from Data and functionality. This is the WPF way. Never mix UI with data / business code.

The Command in the ViewModel serves as an abstraction for the Button. The ViewModel doesn't know what a Button is, nor should it. Let me know if you need further details.

Upvotes: 2

Related Questions