0

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.

Tony Rush
  • 199
  • 5
  • 13
  • Kind of confused. Which properties in the button are you talking about and how would there be an overkill since in the DataTemplate you define the template and associated Styles "once", which would then be reused for every element from your collection. – Viv Mar 28 '13 at 20:10
  • For this example, I just want to maintain the Content, CustomProperty, and Click Properties of CustomButton. This isn't overkill now, but was thinking if I had 10+ Properites and had to map each and every one of them instead of there just being a way to "auto-bind". If we are forced to manually bind everything, I think I know how to do CustomProperty with a DependencyProperty but have no idea how I would persist the Click event through a DataTemplate. Thanks for the comment. – Tony Rush Mar 28 '13 at 20:13
  • Dude, I don't know what `Screen` is, but your `Data Items` should not contain instances of `UI Elements`. That is, you cannot place something like `ScreenButton` in a ViewModel. – Federico Berasategui Mar 28 '13 at 20:33
  • @HighCore +1 to you if that's what the OP is doing. I assumed he has three classes in his code behind file and wasnt using MVVM after seeing MainWindow in the mix – Viv Mar 28 '13 at 20:40
  • As for the event you just wire up the event handler to the click event. – paparazzo Mar 28 '13 at 20:40
  • @HighCore, I don't follow. You're saying I can't put a Button in a ListView row? – Tony Rush Mar 28 '13 at 20:47
  • your datatemplate should target datatype custombutton and you put the datatemplate in app resources so you only define it once. – failedprogramming Mar 28 '13 at 20:48
  • 1
    @TonyRush I'm saying you should not mash together `UI` and `Data`. Your `ScreenRequest` class doesn't derive from `FrameworkElement` so I must assume it's a `Data` class, not a `UI` class. That should not contain references to anything derived from `FrameworkElement`. – Federico Berasategui Mar 28 '13 at 20:50
  • @TonyRush it would be easier if you posted a screenshot of what you really need. So that we can tell you the MVVM way to do it. – Federico Berasategui Mar 28 '13 at 20:54
  • @HighCore, I can't post a picture because I don't have enough reputation points. Seems silly. But what I want is a 2 column table, first row is a button (CustomButton) that will perform differently depending on that button's CustomProperty. The second column is just text. I have this much now, except I don't know how to carry over the click and CustomProperty. Any guidance with MVVM to change the way I'm doing it now though would be awesome. – Tony Rush Mar 28 '13 at 21:09
  • @TonyRush Again, [UI is not Data](http://stackoverflow.com/questions/14381402/wpf-programming-methodology/14382137#14382137), you don't need a Custom button to perform different actions. Also, you should not do event-based programming inside a `DataTemplate`, which means that you should avoid event-based stuff at all. a `Button` is represented in the ViewModel by a `Command` (`DelegateCommand` or similar), which then can perform different actions based on ViewModel data, not UI elements' properties. – Federico Berasategui Mar 28 '13 at 21:12
  • Thanks @HighCore. I have a lot of research to do, obviously new to this and your link makes sense but I have to practice. I will go the MVVM route and post my progress later. – Tony Rush Mar 28 '13 at 21:18

1 Answers1

2
<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.

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • This is awesome. Who doesn't learn best by relevant examples? I have your code running, but want to spend the next couple hours truly understand how it all works. I will comment back later. Thanks so much! :) – Tony Rush Mar 28 '13 at 21:42
  • Okay, I understand the ViewModel class but not the inner-workings of the Command class yet, hopefully that will come with time. For a project I'm working on, I've taken your example one step further and am displaying a List of items in each row and my columns are dynamically generated to be bound to the correct index. E.g. the binding for column 1 is TheList[0].Text, the binding for column 2 is TheList[1].Text. This works great, my only problem now is that when I click the Button it takes me into the object instance which contains the current row's list, so I don't actually know... – Tony Rush Mar 29 '13 at 03:20
  • ...which column (button) was clicked on. May I share with you a Visual Studio solution? I guess I'm looking for input on whether I'm going down the right path or whether I need to reevaluate my strategy. I hope that makes sense even. – Tony Rush Mar 29 '13 at 03:21
  • @TonyRush that's a subject for another question... post it separately if you want.. and I can help you there.. – Federico Berasategui Mar 29 '13 at 03:22