StolenKitten
StolenKitten

Reputation: 651

Why isn't this data binding working?

So I'm brand new to WPF data binding, and it is.. complicated. At this point, I'm trying to just create a list of premade test items and have it displayed in a listbox with a data template when I press a button. After hours of puzzling through tutorials and MSDN this is the best I could come up with.

The data item I want to make a list from:

class ListingItem
{
    private string title;
    private string user;
    private string category;

    //Dummy constructor for test purposes
    public ListingItem()
    {
        title = "TestTitle";
        user = "TestUser";
        category = "TestCatagory";
    }
}

The quick and dirty list creator:

class ListMaker
{
    public static List<ListingItem> getListing()
    {
        List<ListingItem> listing = new List<ListingItem>();
        for(int i = 0; i <100; i++)
        {
            listing.Add(new ListingItem());
        }
        return listing;
    }
}

The XAML of the list itself:

<ListBox x:Name="Listing">
<ListBox.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding user}"/>
                <TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding category}"/>
            </StackPanel>
            <TextBlock Foreground="Black" Width="270" TextWrapping="Wrap" Text="{Binding title}"/>
        </StackPanel>
    </DataTemplate>
</ListBox.ItemTemplate>

And finally, the button click event which is SUPPOSED to make the magic happen:

private void TabClickEvent(object sender, RoutedEventArgs e)
    {
        Listing.DataContext = RedditScanner.getListing();
    }

Problem is, obviously, the magic is not happening. No errors or anything so easy, I just press that button and dont see any change to the list box. Any help with this?

Upvotes: 1

Views: 185

Answers (4)

StolenKitten
StolenKitten

Reputation: 651

There were only 2 problems with my code, which I found:

  1. The properties were set as private in ListingItem, which Henk Holterman caught (+1ed)

  2. I wasn't setting ItemSource on the list anywhere.

I didn't need to do any of the other stuff Peter Trenery mentioned at all.

Upvotes: 0

Peter Trenery
Peter Trenery

Reputation: 781

Made some minor changes to your code as explained below.


class ListingItem
    {
        public  string title { get; set; }
        public string user { get; set; }
        public string category { get; set; }
    
        //Dummy constructor for test purposes
        public ListingItem()
        {
            title = "TestTitle";
            user = "TestUser";
            category = "TestCatagory";
        }
    }
  • The list item class, I changed the title, user and category to properties (get;set;). I also needed to make them public so they could be accessed through the binding.

    class ListMaker
    {
        public static List getListing()
        {
            List listing = new List();
            for (int i = 0; i < 100; i++)
            {
                listing.Add(new ListingItem());
            }
            return listing;
        }
    }
  • No changes to your ListMaker class

    public class CommandHandler : ICommand
    {
        private Action _action;
        private bool _canExecute;
        public CommandHandler(Action action, bool canExecute=true)
        {
            _action = action;
            _canExecute = canExecute;
        }

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

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _action();
        }
    }
  • I introduced a new class to be able to bind the button. This kind of class if relatively common

<Window x:Class="SimpleDatabinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:SimpleDatabinding"
Title="MainWindow" Height="350" Width="525">

<Window.DataContext>
<viewmodel:MainWindowViewModel/>
</Window.DataContext>

<Grid>
<DockPanel>
<Button Command="{Binding FillListCommand}" DockPanel.Dock="Top">Fill List</Button>

<ListBox ItemsSource="{Binding Listing}" DockPanel.Dock="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding user}"/>
<TextBlock Foreground="Gray" Margin="25,0,0,0" Text="{Binding category}"/>
</StackPanel>
<TextBlock Foreground="Black" Width="270" TextWrapping="Wrap" Text="{Binding title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Grid>
</Window>

  • Note the addition of xmlns:viewmodel="clr-namespace:SimpleDatabinding". SimpleDatabinding was the name of the project. It's used to locate the view model in the datacontext below.
  • The Window.DataContext binds the WPF page to the view model. I called my class MainWindowViewModel (see below). This will automatically create an instance of the view model and bind it to the window.
  • I introduced a button to click. It's bound to a command FillListCommand. I'll define that in the view model below.
  • I updated the ItemsSource on the ListBox to be bound to the Listing property.
  • Other than that, I think it's the same.



class MainWindowViewModel : INotifyPropertyChanged 
{
    public event PropertyChangedEventHandler PropertyChanged;

    public List Listing { get; set; }
    public CommandHandler FillListCommand { get; set; }

    public MainWindowViewModel()
    {
        FillListCommand = new CommandHandler(DoFillList);
    }

    public void DoFillList()
    {
        Listing = ListMaker.getListing();
        ProperyHasChanged("Listing");
    }

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

  • Finally in the viewmodel class, I implemented the INotifyPropertyChanged interface. This is the mechanism to notify the UI that a value on your view model has changed. In most implementations, this is wrapped in some sort of ViewModel base class but I left it in so you could see it.
  • As above, I converted the Listing variable to a public property (get;set;) so it could be accessed through the binding.
  • I created a CommandHandler property called FillListCommand. This uses the class above. The button is bound to this variable. The constructor of the view model initializes and points it to the function to be called when the button is clicked.
  • Finally, in the DoFillList function, I initialize Listing as you had it but I also use the notification to let the UI know it's changed.

Sorry about all the writing. Hope this is somewhat helpful. I don't think it's too different from what you had.

Upvotes: 1

AnotherDeveloper
AnotherDeveloper

Reputation: 1272

Don't forget to decorate your data members and service methods with the appropriate tags.

These short videos are great for learning WCF: http://channel9.msdn.com/Shows/Endpoint?sort=rating#tab_sortBy_rating

Upvotes: 0

Henk Holterman
Henk Holterman

Reputation: 273179

You cannot bind to private fields. Not even to public fields I think.

Use properties:

class ListingItem
{
    //private string title;
    //private string user;
    //private string category;

    public string Title { get; set; }
    public string User { get; set; }
    public string Category { get; set; }

    //Dummy constructor for test purposes
    public ListingItem()
    {
        Title = "TestTitle";
        User = "TestUser";
        Category = "TestCatagory";
    }
}

And for full databinding you would have to implement INotifyPropertyChanged on ListingItem.

the magic is not happening. No errors or anything so easy,

Keep an eye on the Output Window during execution. Binding errors are reported.

Upvotes: 2

Related Questions