Binding ObservableCollection of objects trouble - c#

I'm trying to code an rssreader and would be pleased for some architecture hints.
My reader main window hosts two wpf pages which are loaded into frames, it's a "bottombar" where user can select different rss providers. In the main frame (or page) is my listview.
Because of an loading animation and UI Freeze I've an extra class with a backgroundworker which fills an observable collection with RSS Data, when I'm debugging, it fills my collection correctly.
In main page i'm setting the datacontext to this observable collection but listview doesn't show anything, here I'm stuck.
That's what I have:
MainPage XAML:
> <ListBox ItemsSource="{Binding}" DisplayMemberPath="RssTitle"
> IsSynchronizedWithCurrentItem="True"
> SelectionChanged="itemsList_SelectionChanged"
> ItemContainerStyle="{DynamicResource listboxitem_style}" Height="396"
> HorizontalAlignment="Left" Margin="126,12,0,0" Name="ListBox1"
> VerticalAlignment="Top" Width="710"></ListBox>
ListBox1.DataContext = GetRssItems.observable_list;
Bottompage to get another rss feed:
GetRssItems getitems = new GetRssItems();
GetRssItems.observable_collection = null;
getitems.start_bg_worker("url");
GetRssItems.cs
public class GetRssItems
{
public static ObservableCollection<RSSItem> observable_collection { get; set; }
public static string tmp_url;
public BackgroundWorker worker = new BackgroundWorker();
public void start_bg_worker(string url)
{
if (!worker.IsBusy)
{
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(url);
}
}
}
In BackgroundWorkers DoWork I'm receiving rss items with linq and add it to my observable collection:
observable_collection.Add(new RSSItem(item.tmp_Title, item.tmp_Link, item.tmp_Description, item.tmp_pubDate, item.tmp_ImageUrl));
Seperate class RSSItem.cs
public class RSSItem
{
public string RssTitle { get; set; }
public string RssLink { get; set; }
public string RssDescription { get; set; }
public string RsspubDate { get; set; }
public string RssImageUrl { get; set; }
public RSSItem(string rsstitle, string rsslink, string rssdescription, string rsspubdate, string rssimageurl)
{
RssTitle = rsstitle;
RssLink = rsslink;
RssDescription = rssdescription;
RsspubDate = rsspubdate;
RssImageUrl = rssimageurl;
}
}
Thanks for your time and hints.
Best Regards

You need to read up a bit MVVM to get the most benefit from WPF. Your line setting the listbox's datacontext is rather confusing.
What you should have is your main window's (xaml) data context set to a view model class that contains your observable collection. The list box's ItemsSource is set to that property name.
For example:
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<RSSItem> RSSItems
{
get;
set;
}
// Other stuff applicable to the main window.
}
When the view is constructed, pass an instance of the MainViewModel to it's DataContext. Then the Xaml for the ListBox would be:
<ListBox ItemsSource={Binding Path=RSSItems} ... />
If you want to be able to set/change the RSSItems collection instance (I.e. public setter) then you should set it up it's setter with the NotifyPropertyChanged event, however if you just add/remove items then this shouldn't be necessary. (I.e. loading populating the items in the constructor.)

use the following:
the data context should be the Object getitems
<ListBox ItemsSource="{Binding observable_collection}" Height="167" Margin="0" Name="listBox1" Width="330" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding RssTitle}" FontWeight="Bold" FontSize="16" />
<TextBlock Text="{Binding RssLink}" FontSize="16"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
PS:
your naming is HORRBILE

Related

Multiple rows in datagrid are changing when only the row explicitly changed should be

I have a two datagrids, both using identical xaml (other than one having an extra column). One is in the main window, the other is inside a usercontrol. Both are bound to a collection of the same items (one is currently an ObservableCollection, the other is an array). Project uses MVVM (or a close approximation).
The array/main window one works correctly - the Location combobox loads in the selected value (where it isn't null) and changing that value only affects the row it is changed in.
The ObservableCollection/usercontrol one does not. The values in the Location column do not load when the window opens, until you scroll down then back up again. Changing the Location combobox changes the value displayed in every visible row (or every row if virtualisation is turned off)... including in the disabled comboboxes. The same does not happen for the Bonding Level combobox, which is handled essentially identically. This happens whether the data presented is an array or an ObservableCollection.
Each instance of the class used to produce a row should be entirely separate, there's no codebehind to mess with values, and extremely similar xaml and c# in another file using the exact same type in the collection works. I am lost!
Screenshots are as follows:
Correct behaviour (Main window, array)
https://i.imgur.com/SJIsTOT.png (Loaded immediately, no values in disabled comboboxes)
https://i.imgur.com/cmjaPoR.png (Single row changes)
Broken behaviour (Usercontrol embedded into tabcontrol, ObservableCollection)
https://i.imgur.com/yC3iAas.png (not loading on window opened)
https://i.imgur.com/aQgPMCN.png (loaded after scroll, including invalid values)
https://i.imgur.com/dqo39aB.png (one combobox changed, all rows change)
XAML for the DataGrid and the misbehaving combobox column (no binding errors):
<DataGrid Grid.Row ="1" Width="Auto" Height="Auto" AlternatingRowBackground="#FBE9D9" AlternationCount="2"
AutoGenerateColumns="False" GridLinesVisibility="None"
ItemsSource="{Binding Familiars}" IsSynchronizedWithCurrentItem="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding OpenDataFamiliarWindow}" CommandParameter="{Binding Familiars/}"/>
</DataGrid.InputBindings>
<DataGrid.Resources>
<local_c:OwnedToBoolConverter x:Key="OwnedToBoolConverter"/>
<local_c:EnemyTypeToColourConverter x:Key="EnemyTypeToColour"/>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
</DataGrid.Resources>
<DataGridTemplateColumn Header="Location" Width="Auto" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding AvailableLocationTypes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="90"
SelectedItem="{Binding Info.Location, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True" IsEnabled="{Binding Info.Owned, Converter={StaticResource OwnedToBoolConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource DescConverter}, Mode=OneTime}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
The class that the UserControl is designed to bind to (and the only bit that differs from the working use of essentially the same datagrid):
public class ColiseumVenue : INotifyPropertyChanged
{
public BitmapImage HeaderImage { get; private set; }
public string Name { get; private set; }
public ObservableCollection<FamiliarViewModel> Familiars { get; private set; } = new ObservableCollection<FamiliarViewModel>();
private IModel m_Model;
public ColiseumVenue(IModel model, string name, IEnumerable<FamiliarViewModel> familiars)
{
Name = name;
m_Model = model;
HeaderImage = ImageLoader.LoadImage(Path.Combine(ApplicationPaths.GetViewIconDirectory(), name + ".png"));
foreach(var familiar in familiars)
{
Familiars.Add(familiar);
}
}
public ColiseumView Window { get; set; }
private BaseCommand m_openDataFamiliarWindow;
public ICommand OpenDataFamiliarWindow
{
<snip>
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
The public variables of the FamiliarViewModel class are:
public class FamiliarViewModel : INotifyPropertyChanged
{
public FamiliarInfo Info { get; set; }
public LocationTypes[] AvailableLocationTypes { get; private set; }
public OwnershipStatus[] AvailableOwnershipStatuses { get; private set; }
public BondingLevels[] AvailableBondingLevels { get; private set; }
public BookmarkState[] AvailableBookmarkStates { get; private set; }
}
The parts of the FamiliarInfo class that are bound to are:
public ICRUD<OwnedFamiliar> OwnedFamiliar
{
get { return m_OwnedFamiliar; }
set
{
if(m_OwnedFamiliar != value)
{
m_OwnedFamiliar = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Owned"));
}
}
}
public OwnershipStatus Owned => OwnedFamiliar != null ? OwnershipStatus.Owned : OwnershipStatus.NotOwned;
public BondingLevels? BondLevel
{
get
{
return OwnedFamiliar?.Fetch()?.BondingLevel;
}
set
{
if (value.HasValue)
{
OwnedFamiliar?.Update(f => f.BondingLevel = value.Value);
}
}
}
public LocationTypes? Location
{
get
{
return OwnedFamiliar?.Fetch()?.Location;
}
set
{
if (value.HasValue)
{
OwnedFamiliar?.Update(f => f.Location = value.Value);
}
}
}
This turned out to be the same issue as: WPF MVVM DataGridComboboxColumn change one row updates all
The selected answer (setting IsSynchronizedWithCurrentItem to null) worked.

How to pass data from DataContext to ListBox in WPF?

I have a class defined like:
public class Agent
{
public int Id { get; set; }
public string Category { get; set; }
// rest removed for brevity
}
Then, in WPF, I get the data as List and pass it to DataContext as this:
List<Agent> agents; // this includes my data
this.DataContext = agents;
And in .xaml part I want to list the Category field of each object. I have something like this:
<ListBox
Name="agentCategoryListBox"
Grid.Row="2"
Grid.Column="1"
ItemSource="{Binding Path=Category"} />
But this doesn't seem to work correctly. Any ideas?
Let me help you to do this in the correct way as Alex suggested.
Create a list and populate it in ViewModel like this
ViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
agents = new ObservableCollection<Agent>();
LoadData();
}
private void LoadData()
{
agents.Add(new Agent { Id = 1, Category = "a" });
agents.Add(new Agent { Id = 2, Category = "b" });
agents.Add(new Agent { Id = 3, Category = "c" });
}
}
In XAML, Make your list and use data template like this:
<Window.Resources>
<DataTemplate x:Key="AItemTemplate">
<TextBlock Text="{Binding Category}"></TextBlock>
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding agents}"
ItemTemplate="{StaticResource AItemTemplate}"></ListBox>
That is it !!
Normally the DataContext would be a view model class that would contain the list of agents; then you can bind the ItemsSource to that list. Any of the many examples that deal with listbox will be pretty straight forward when it comes to that. Not really sure how the binding should look like if the list itself is the DataContext.
Then once the ItemsSource is set to a list of agents, if you want to show the Category in the list, the simpler way is to set DisplayMemberPath to "Category".
I suggest looking into MVVM and learning to apply it, it's an invaluable concept in my opinion.
You try to bind your listbox to a string property.
You can try this :
Give a name to your user control for exemle myUC
Add a property to your user control :
public List<Agent> AgentList { get; set; };
Fill your agentlist :
this.AgentList = //fill method
And bind your listbox like this :
<ListBox
Name="agentCategoryListBox"
Grid.Row="2"
Grid.Column="1"
ItemSource="{Binding Path=AgentList, ElementName=myUC"} />
may be this will give you an idea:
http://www.c-sharpcorner.com/forums/wpf-datacontext-binding-with-listbox
The fastest way to get what you want is :
<ListBox
Name="agentCategoryListBox"
Grid.Row="2"
DisplayMemberPath="Category"
Grid.Column="1"
ItemSource="{Binding Path=."} />
ItemsSource is binded directly to your Datacontext (which is your list) And then you tell to your ListBox to display the property Category.
But the proper way would have been :
1 - Create a DataContext
public class AgentsDC
{
public List<Agent> Agents { get; set; }
}
2 - Give this class as DataContext
this.DataContext = new AgentsDC();
3 - Bind all these things
<ListBox
Name="agentCategoryListBox"
Grid.Row="2"
DisplayMemberPath="Category"
Grid.Column="1"
ItemSource="{Binding Path=Agents"} />
I also would suggest you to use MVVM. But if you do not want to then try this.
XAML:
<ListBox Name="AgentCategoryListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Category}" d:DataContext="{d:DesignData}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CS:
public MainWindow()
{
InitializeComponent();
List<Agent> agents = new List<Agent>
{
new Agent
{
Category = "Category"
}
};
DataContext = agents;
}
public class Agent
{
public string Category
{
get;
set;
}
}

MVVM Binding Observable Collection to view?

In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.

Databinding ObservableCollection to a ListBox

I have been trying to figure out the proper way to bind an ObservableCollection of a class to a ListBox with a TextBox DataTemplate. I've tried to implement the code in WPF binding: Set Listbox Item text color based on property but that hasn't gotten me very far as of yet. I'm new to WPF DataBinding, having at most programatically set the ItemsSource in simple cases.
I have this class
public class item
{
public string guid;
public bool found;
public bool newItem;
public Brush color;
}
and the following ObservableCollection
public ObservableCollection<item> _items;
public Window()
{
InitializeComponent();
_items = new ObservableCollection<item>();
}
Elsewhere in the code I add items to the collection via
_items.Add(new item() { guid = sdr.GetString(0), found = false, newItem = false, color = Brushes.Red });
Here's simplified XAML for the ListBox
<ListBox x:Name="ListBox_Items">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text=GUID_HERE Foreground=COLOR_HERE/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've tried several different ways to get this to work properly, yet for none of them the ListBox is updating. Is anyone able to help point me in the right direction here?
Four things:
Your item class needs to use public properties:
public class item
{
public string guid { get; set; }
public bool found { get; set; }
public bool newItem { get; set; }
public Brush color { get; set; }
}
You need to set the ItemsSource to the collection, and set the current DataContext
public Window()
{
InitializeComponent();
DataContext = this;
_items = new ObservableCollection<item>();
ListBox_Items.ItemsSource = _items;
}
You need to update your DataTemplate to use the property names of your POCO
<ListBox x:Name="ListBox_Items">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding guid}" Foreground="{Binding color}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I think you forgot to bind your ListBox to collection itself.
Your XAML should look like:
<ListBox x:Name="ListBox_Items" ItemsSource="{Binding _items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text=GUID_HERE Foreground=COLOR_HERE/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And if you want to change properties of items in your collection (and that changes to appear on UI) you should implement INotifyPropertyChanged (see more at MSDN) interface in "item" class.

Having trouble binding ViewModel to ComboBox

I have a viewmodel setup as the following
public class cDriveListVM
{
public string Drive { get; set; }
public cDriveListVM(string name)
{
Drive = name;
}
}
I declare the observablecollection in the window and set its datacontext to this observable collection.
public ObservableCollection<cDriveListVM> DriveList { get; set; }
private void dl()
{
DriveList = new ObservableCollection<cDriveListVM>();
DriveList.Add(new cDriveListVM("drive 1"));
DriveList.Add(new cDriveListVM("drive 2"));
this.DataContext = DriveList;
}
Xml for combobox:
<ComboBox x:Name="Drive_ComboBox" ItemsSource="{Binding Path=Drive}" HorizontalAlignment="Center" IsReadOnly="True" Grid.Column="0" Grid.Row="0" Width="300" Margin="10" SelectionChanged="Drive_Changed" Height="22" VerticalAlignment="Top"/>
I am just learning how to use Viewmodel so I am unsure what I am doing wrong, any help would be appreciated. I updated the xml file it results in the following combbox.
There are a few problems with this code.
One, the binding is set up wrong. Since the property with the viewmodel collection is DriveList, the binding should be ItemsSource="{Binding Path=DriveList}".
Two, you are attempting to display a field from your viewmodel, which is not doable. WPF's binding engine only works with properties, so the viewmodel should have a property:
public string Drive { get; set; }
And finally, the DisplayMemberPath should match the property name from the viewmodel: DisplayMemberPath="Drive".
Update: I just noticed that the DataContext is the observable collection itself -- I probably missed it on the first read. In that case, you want to bind directly to the data context:
ItemsSource="{Binding}"
And set DisplayMemberPath to the property you want to display:
DisplayMemberPath="Drive"

Categories