Update bound control - c#

I'm learning WPF. I figured out how to bind to a list box using the following code.
XAML
<ListBox Name="lstUpdates" Grid.Row="1" Grid.Column="0" Margin="5,0,5,5" ItemsSource="{Binding HistoryData}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Loaded="lstUpdates_Loaded">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Header}" FontWeight="Bold" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" Height="46" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CS
public class HistoryRow
{
public string Header { get; set; }
public string Description { get; set; }
public int ArticleUpdateId { get; set; }
}
public IEnumerable<HistoryRow> HistoryData { get; set; }
DataContext = this;
HistoryData = new List<HistoryRow>
{
new HistoryRow { Header = "10/29/1961", Description = "Blah blah" },
new HistoryRow { Header = "12/2/1976", Description = "Blah blah" },
new HistoryRow { Header = "5/24/1992", Description = "Blah blah" },
new HistoryRow { Header = "2/18/2012", Description = "Blah blah" },
};
I've got this so it works pretty well. But now my question is how can I refresh my listbox when my data changes. I've found several "solutions" on the Web and none of them work for me.

You need to inform a view about changes of your collection.
When using ObservableCollection as source of ListBox control, the view listening a event CollectionChanges and update control when event get raised.
So change type of HistoryData to ObservableCollection<YourHistoryDataType>
To work properly you need keep same instance of collection and update only items.
To keep same instance create a property on your viewmodel:
private ObservableCollection<HistoryRow> _HistoryData;
public ObservableCollection<HistoryRow> HistoryData
{
get
{
return _HistoryData;
}
set
{
If(Equals(_HistoryData, value) return;
_HistoryData = value;
this.OnPropertyChange(nameof(this.HistoryData);
}
}
Calling OnPropertyChanged will inform view about that instance of collection was changed.
Read this: Implementing the Model-View-ViewModel Pattern
There are some information about ObservableCollection and PropertyChanged event

Your class HistoryRow will need to extend INotifyOfPropertyChange,
Then instead of using an Ienumerable, it needs to be of type ObservableCollection and subscribe to PropertyChangeEvent
private ObservableCollection<HistoryRow> _historyData;
public ObservableCollection<HistoryRow> HistoryData
{
get {return _historyData}
set {_historyData = value; OnPropertyChange("HistoryData");}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}

My solution: Don't use binding. Just populate the list by creating a collection and assigning it to the ItemsSource property. Then, when the data has changed, do it again.
I don't know why WPF has to make so many things a pain, but when it's difficult to get a clear answer on stackoverflow on how to refresh the list, or I'm looking into reading a bunch of articles for such a simple task, it's probably a good indication I'm on the wrong track.
Perhaps I will find a reason to get into MVVM design and binding. But for now, all I want to do is update my ListBox.

Related

Binding a Listbox to a ObservableCollection

So I am trying to bind the following ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListBoxItem> _PlacesOrCities;
public ObservableCollection<ListBoxItem> PlacesOrCities
{
get { return _PlacesOrCities; }
set { _PlacesOrCities = value; RaisePropertyChanged("PlacesOrCities"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
_PlacesOrCities = new ObservableCollection<ListBoxItem>();
}
}
To the following xaml:
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=(gms:MainWindow.ViewModel).PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the codebehind as such:
public ViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new ViewModel();
DataContext = ViewModel;
}
And upon firing a button click event- I try to set the values of the observable collection using a in memory list:
private void StateProvince_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_CurrentSelectionPlaces = Canada.Provinces
.FirstOrDefault(x => x.Abbreviation == _SelectedStateProvince_ShortName)
.Place.OrderBy(x => x.Name).ToList();
foreach (var currentSelectionPlace in _CurrentSelectionPlaces)
{
ViewModel.PlacesOrCities.Add(currentSelectionPlace);
}
}
But it seems like none of the items are being added to the collection. Am I binding it incorrectly?
I've tried quite a few solutions but none of them seem to change the result- where no items in the list are being loaded into the collection properly.
EDIT:
It may be worth noting that the ListBoxItem as seen in the ViewModel is a custom model:
public class ListBoxItem
{
[J("Name")] public string Name { get; set; }
[J("PostalCodes")] public string[] PostalCodes { get; set; }
public Visibility Visibility { get; set; } = Visibility.Visible;
}
You should try to fit to the MVVM pattern, so the population of the list should occur at viewmodel level and not in the view's code behind.
You mentioned that you use a click event, instead of doing so, try to bind the command property of the button to a command in the viewmodel, see this link with an explanation of several types of commands and how to use them: https://msdn.microsoft.com/en-us/magazine/dn237302.aspx
In the other hand, if you already set the data context in the window constructor, to bind the ListBox items source you only need the name of the property to bind, "PlacesOrCities":
<ListBox Name="lbPlacesCity" ItemsSource="{Binding Path=PlacesOrCities, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate DataType="models:ListBoxItem">
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Name}" Visibility="{Binding Visibility}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It would also be recommendable trying to load the items in the list without any template, you can use ListBox DisplayMemberPath property to display the name, and once you are able to load items, apply the style.
Also in the way you use ObservableCollection, you actually need to replace the whole collection instead of adding to fire RaisePropertyChanged, try a normal property instead.
public ObservableCollection<ListBoxItem> PlacesOrCities {get;set;} = new ObservableCollection<ListBoxItem>();
Modifying the collection will update the UI, so whenever you use Add or Clear, the UI should know it.
Hope it helps.

Binding a combo box to an ObservableCollection

I have a wpf c# application.
I am using a combo box and I have set its itemsource property to an observable collection.
The problem I have is that when I modify this collection the changes are not reflected in my drop down.
so i am wondering what I have done wrong?
This is my class object:
public class JobTicker
{
public string CustomerRef { get; set; }
public string JobRef { get; set; }
public int JobId { get; set; }
public string CustomerJobDetails { get; set; }
public string CustomerName { get; set; }
}
I bind to my collection:
ActiveState.JobsActive = new ObservableCollection<JobTicker>('data from a list');
my declaration of the collection variable:
public static ObservableCollection<JobTicker> JobsActive = new ObservableCollection<JobTicker>();
My combo Box (which is on a userControl of mine that is loaded when my app starts)
<xctk:WatermarkComboBox x:Name="cboActiveJobs" Grid.Row="1" Grid.Column="2"
Width="250" Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
and my code behind:
cboActiveJobs.ItemsSource = ActiveState.JobsActive;
Now if I modify 'ActiveState.JobsActive' I would expect changes to be reflected in my dropdown but they are not.
The code you have isn't actually binding it. It's just assigning a collection to a property.
The combo box's ItemsSource property can't listen for notifications from the ObservableCollection. Instead, you need an instance of the Binding class to listen for those notifications and make the UI updates happen. Binding is where all the magic is. You could create one programmatically in code behind and attach it (see links below), but the easiest and by far most common way is to bind in XAML:
<xctk:WatermarkComboBox
ItemsSource="{Binding JobsActive}"
SelectedItem="{Binding SelectedCustomer}"
x:Name="cboActiveJobs"
Grid.Row="1"
Grid.Column="2"
Width="250"
Watermark="Select Customer"
DisplayMemberPath="CustomerJobDetails"
HorizontalContentAlignment="Center"
SelectionChanged="cbo_SelectionChanged"
DropDownOpened="cbo_DropDownOpened"
DropDownClosed="cbo_DropDownClosed"
Style="{StaticResource ComboBoxFlatStyle}"
/>
Now, JobsActive should be a public property of the view model that the DataContext for that control. If it isn't, that won't work.
Since you've got a SelectionChanged event, I also added a SelectedCustomer binding, which would be a property on your view model as well. The Binding will update this both ways: Change it in your viewmodel, and the combobox selection will change. When the user picks a combobox item, the viewmodel's property value will change.
private JobTicker _selectedCustomer;
public JobTicker SelectedCustomer {
get { return _selectedCustomer; }
set {
_selectedCustomer = value;
// If you're not in C#6, use this instead:
//OnPropertyChanged("SelectedCustomer");
OnPropertyChanged(nameof(SelectedCustomer));
}
}
// Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
If you do want to get this binding working right away without writing a viewmodel, I don't recommend that approach, but it's absolutely doable. There are several answers on StackOverflow that should help with getting that working: WPF Binding Programatically, How to programmatically set data binding using C# xaml.

Binding ObservableCollection in viewmodel to listbox

I'm very new to MVVM and bindings and I'm trying to learn to work with it.
I run into the problem of binding my viewmodel to the view in particular binding an observable collection to a listbox.
this is what my viewmodel looks like:
namespace MyProject
{
using Model;
public class NetworkViewModel: INotifyPropertyChanged
{
private ObservableCollection<Person> _networkList1 = new ObservableCollection<Person>();
public ObservableCollection<Person> NetworkList1 //Binds with the listbox
{
get { return _networkList1; }
set { _networkList1 = value; RaisePropertyChanged("_networkList1"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public NetworkViewModel()
{
_networkList1 = new ObservableCollection<Person>()
{
new Person(){FirstName="John", LastName="Doe"},
new Person(){FirstName="Andy" , LastName="Boo"}
};
}
}
in the view I have
namespace MyProject
{
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
}
and in the XAML I have
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock >
<Run Text="{Binding Path=FirstName}"/>
<Run Text="{Binding Path=LastName}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems like you might have a typo in your view model.
RaisePropertyChanged("_networkList1");
You want to raise the property changed notification for the public property not the private variable.
RaisePropertyChanged("NetworkList1");
This might be preventing your view from updating properly.
In addition to Gaurav answer, if _networkList1 is a private field in your NetworkViewModel class, how is it possible to get access to it in Networking window? I mean what's the meaning of the following line?
lb1.ItemsSource = _networkList1;
when you define a Property (NetworkList1), you have to use it in order to get advantages of its features (e.g. to get RaisePropertyChanged working). Otherwise what's the point, you could have just defined a field (_networklist1). So changing
_networkList1 = new ObservableCollection<Person>()
to
NetworkList1 = new ObservableCollection<Person>()
results in actually setting NetworkList1 and therefore RaisePropertyChanged("NetworkList1") to be fired. (however if you want to just show data in a your listbox this is unnecessary)
and if i'm getting it right, changing this:
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
to
public partial class Networking : Window
{
public NetworkViewModel MyViewModel { get; set; }
public Networking()
{
InitializeComponent();
MyViewModel = new NetworkViewModel();
this.DataContext = MyViewModel;
}
}
should get your binding to work.
*Note that when you set DataContext to NetworkViewModel, then the binding in
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
works, because NetworkList1 is a Property of NetworkViewModel.
Do not call RaisePropertyChanged() method on ObservableCollection<T>, for god's sake. This is a common mistake in a majority of cases (however, there are cases, where you need to reset ObservableCollection<T> using new keyword, but they are kinda rare).
This is a special type of collection which notifies UI internally about all the changes of its content (like add, remove etc.). What you need is to set the collection using new keyword once in a lifetime of your ViewModel, and then manipulate your items via Add(T item), Remove(T item), Clear() methods etc.
and UI will get notified about it and updated automatically.

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?
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.
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.
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
There were only 2 problems with my code, which I found:
The properties were set as private in ListingItem, which Henk
Holterman caught (+1ed)
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.

Bind collection to combobox

I have this combobox
<ComboBox Height="30" SelectedIndex="0" Margin="5 3 5 3" Width="170" ItemsSource="{Binding WonderList}" SelectedValuePath="selectedWonder">
<ComboBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Source="{Binding Path}" Height="20"></Image>
<Label Content="{Binding Name}" Style="{StaticResource LabelComboItem}"></Label>
</WrapPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
where I want to show items as an image plus a text.
This is the business class for the objects in the item list
public class Wonder: INotifyPropertyChanged
{
private string name;
private string path;
public event PropertyChangedEventHandler PropertyChanged;
#region properties, getters and setters
public String Name { get; set; }
public String Path { get; set; }
#endregion
public Wonder(string name, string path)
{
this.name = name;
this.path = path;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
and the code behind of the window
public class Window1 {
public List<Wonder> WonderList;
public Window1()
{
InitializeComponent();
WonderList = new List<Wonder>();
WonderList.Add(new Wonder("Alexandria", "Resources/Images/Scans/Wonders/Alexandria.jpg"));
WonderList.Add(new Wonder("Babylon", "Resources/Images/Scans/Wonders/Babylon.jpg"));
}
}
I´m pretty new to this xaml "magic" and guess I dont understand correctly how the data binding works, I think that with ItemsSource="{Binding WonderList}" it should take the collection with that name (from the code behind) and show their Name and Path, but it shows an empty list.
If I do Combo1.ItemsSource = WonderList; in the code behind (I prefer to use the xaml and avoid the code behind), it shows two blank slots but still don´t know how to show the items.
Can you point me in the right direction?
Thanks
If you want to bind like this ItemsSource="{Binding WonderList}" you have to set the DataContext first.
public Window1()
{
...
this.DataContext = this;
}
Then Binding will find the WonderList in Window1 but only if it is a property too.
public List<Wonder> WonderList { get; private set; }
Next: It is useless to bind to property Name if you assign your value to private field name. Replace your constructor with
public Wonder(string name, string path)
{
this.Name = name;
this.Path = path;
}
Next: Your auto properties ({ get; set; }) will not notify for changes. For this you have to call OnPropertyChanged in setter. e.g.
public String Name
{
get { return name; }
set
{
if (name == value) return;
name = value;
OnPropertyChanged("Name");
}
}
Same thing for WonderList. If you create the List to late in constructor it could be all bindings are already resolved and you see nothing.
And finally use ObservableCollection if you want to notify not for a new list but a new added item in your list.
You are not doing the correct way. Simply saying, you should have a Wonders class holding an ObservableCollection property, which is bound to ComboBox's ItemsSource. You should read MSDN:
http://msdn.microsoft.com/en-us/library/ms752347.aspx

Categories

Resources