Windows Runtime GridView throws ArgumentException when setting ItemsSource property - c#

I have the following two classes for a RageComic reader metro style app I am making.
public class ComicDataWNGroup
{
public ComicDataWNGroup(string title)
{
this.Title = title;
this.Items = new ObservableCollection<ComicDataItem>();
}
public String Title { get; set; }
public ObservableCollection<ComicDataItem> Items { get; private set; }
}
public class ComicDataWNSource
{
private RedditAPI.RedditManager currentManager;
public ComicDataWNSource(RedditAPI.RedditManager currentManager)
{
this.currentManager = currentManager;
Groups = new ObservableCollection<ComicDataWNGroup>();
}
public async Task<int> ObtainRecentContent()
{
SubredditComicDataSource comicList = new SubredditComicDataSource();
await comicList.LoadItems();
var comicSources = comicList.GetAllItems();
for (int i = 0; i < comicSources.Count; i++ )
{
var links = await this.currentManager.GetSubredditLinks(comicSources[i].UrlTitle, 6, 0);
ComicDataWNGroup group = new ComicDataWNGroup("From " + comicSources[i].Title);
for(int j = 0; j < links.Data.Count; j++)
{
var linkItem = (RedditAPI.Objects.Link)links.Data[j];
BitmapImage img = await ThumbnailRetriever.GetThumbnailFromRedditUrl(linkItem.ThumbnailUrl);
ComicDataItem item = new ComicDataItem(linkItem.Title, false, true, 0, img, 0, null, null, Visibility.Visible, Visibility.Collapsed);
group.Items.Add(item);
}
Groups.Add(group);
}
return 0;
}
public ObservableCollection<ComicDataWNGroup> Groups
{
{ get; private set; }
}
}
Note: ComicDataItem is a class that mostly contains get set properties. I can post it later if it is needed.
Using these two classes, I am trying to bind the Groups property of the ComicDataWNSource object to a GridView.
The following code is the code I use for the binding.
DataModel.ComicDataWNSource source = new DataModel.ComicDataWNSource(App.Manager);
await source.ObtainRecentContent();
CollectionViewSource viewSource = new CollectionViewSource();
viewSource.Source = source.Groups;
viewSource.IsSourceGrouped = true;
viewSource.ItemsPath = new PropertyPath("Items");
this.itemGridView.ItemsSource = viewSource;
The GridView is the default one generated by Visual Studio when creating a new Grouped Items Page. I already changed the appropriate bindings to match up with the properties in the ComicDataWNGroup class and ComicDataItem class (I can post the XAML for the GridView as well if needed). Also, source.ObtainRecentContent() is currently filling up the Groups array with the correct data. The array of Groups and inner array of Items both have objects in them and neither of them are null.
The problem arises on the line setting the view source to the grid view. Whenever I try it, it throws the following exception:
Argument Exception: Value does not fall within the expected range.
I don't have a clue what I could be doing wrong for this to happen. I already looked at this msdn page already, which shows you how to use a grouped grid view item and view source. Any ideas what I'm doing wrong?

Ok, I managed to make this work by doing the following:
In the xaml where my GridView is defined, I declared the CollectionViewSource in the same xaml file instead of the code behind file.
<Page.Resources>
<CollectionViewSource x:Name="itemsViewSource" ></CollectionViewSource>
</Page.Resources>
I bound the ItemsSource property of the GridView with the following property:
<GridView
x:Name="itemGridView"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
</GridView>
In the code behind, I changed the code to the following:
DataModel.ComicDataWNSource source = new DataModel.ComicDataWNSource(App.Manager);
await source.ObtainRecentContent();
itemsViewSource.Source = source.Groups;
itemsViewSource.IsSourceGrouped = true;
this.itemsViewSource.ItemsPath = new PropertyPath("Items");
By making these changes, the code works. I'm now sure the thing I was doing wrong is that ItemsSource expects a Binding object which points to the CollectionViewSource object, not the actual CollectionViewSource object.

Related

How do I delete from the list?

I'm kind of new to programming and come to quite of a problem.
I'm sure the solution is quite simple but I just can't get my head around it.
I just want to know how to Delete from the listbox and delete the data from the list entirely, because whenever I delete from the list box it disappears but when I add a new layer all the layers that I have deleted come back (so I'm guessing it doesn't really delete from the list?.
This is at the start of my code at the top:
List<Layer> layers = new List<Layer>();
This is my Layers Class:
public class Layer
{
private Image mLayerData = null;
private string mLayerName = "Layer";
public string LayerName
{
get { return mLayerName; }
set { mLayerName = value; }
}
public Image LayerData
{
get { return mLayerData; }
}
public Layer(int width = 500, int height = 500, string layername = "Layer")
{
mLayerData = new Bitmap(width, height);
mLayerName = layername;
}
}
and this is my addLayer Function:
private void addNewLayer()
{
string layerName = "Layer";
layerName += layers.Count;
// Create a default layer in our stack of layers
layers.Add(new Layer(pictureBox1.Width, pictureBox1.Height, layerName));
// Make the picture box talk to the default layer
pictureBox1.Image = layers[0].LayerData;
pictureBox1.Invalidate();
// Update the list of layers
listLayers.Items.Clear();
foreach(Layer l in layers)
{
listLayers.Items.Add(l.LayerName);
}
listLayers.SelectedIndex = listLayers.Items.Count - 1;
}
for my deleteLayer function I have this:
private void deleteLayer()
{
listlayers.Items.RemoveAt(listlayers.SelectedIndex);
}
A better approach would be to use BindingList, which is supports Data Binding and use DataSource property of Listbox to bind the collection.
For example,
BindingList<Layer> layers = new BindingList<Layer>();
listBox.DataSource = layers;
listBox.DisplayMember = nameof(Layer.LayerName);
Also, note that instead of binding/add names of Layer to the Listbox, you should instead bind the Collection of Layer and use the DisplayMember property to ensure the LayerName is displayed in the Listbox.
Now you could Add to the listbox as the following
var layer = new Layer(pictureBox1.Width, pictureBox1.Height, layerName);
layers.Add(newLayer);
Remove
layers.Remove((Layer)listBox.SelectedItem);
The BindingList would refresh the Listbox by itself.

Changing Xamarin Label in ListCell does not work

I have a problem with a ListView. I want each Cell to have a label and a switch but the text of the label does not appear.
Here is my code:
public class FilterPage : ContentPage
{
public FilterPage()
{
List<FilterCell> listContent = new List<FilterCell>();
foreach(string type in Database.RestaurantTypes)
{
FilterCell fc = new FilterCell();
fc.Text = type;
listContent.Add(fc);
}
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
types.ItemsSource = listContent;
var layout = new StackLayout();
layout.Children.Add(types);
Content = layout;
}
}
public class FilterCell : ViewCell
{
private Label label;
public Switch CellSwitch { get; private set; }
public String Text{ get { return label.Text; } set { label.Text = value; } }
public FilterCell()
{
label = new Label();
CellSwitch = new Switch();
var layout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { label, CellSwitch }
};
View = layout;
}
}
If I enter a fixed Text in the FilterCell-Constructor it works fine (e.g.: label.Text = "Hello World")
When I create a Method for the ItemSelected-Event and read out the SelectedItem.Text Property I get the text I assigned as Value but it's never displayed. Only the switch is displayed when I try to run this Code.
Thanks for your help
Niko
Ohh boy. This code looks like a rape (sorry I had to say this).
Now let's see what's wrong:
The reason is you are mixing up data and view heavily.
The line
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
means: "For each item in the list (ItemsSource) create a new filter cell". The FilterCells that you create in the loop are never displayed.
The easy fix
public class FilterPage : ContentPage
{
public FilterPage()
{
var restaurantTypes = new[] {"Pizza", "China", "German"}; // Database.RestaurantTypes
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, ".");
return cell;
});
types.ItemsSource = restaurantTypes;
Content = types;
}
}
There is a standard cell type that contains a label and a switch SwitchCell, use it.
As ItemsSource of your list, you have to use your data. In your case the list of restaurant types. I just mocked them with a static list.
The DataTemplate creates the SwitchCell and sets the Databinding for the Text property. This is the magic glue between View and data. The "." binds it to the data item itself. We use it, because our list contains items of strings and the Text should be exactly the string. (read about Databinding: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/#Data_Binding )
I striped away the StackLayout that contained the list. You can directly set the list as Content of the page.
Lesson
use standard controls, if possible
You should always try to remember to keep data and view apart from each other and use data binding to connect to each other.
Try to avoid unnecessary views.

How to bind the records into DataGridView?

Winforms Application
Code:
var proxy = new ServiceNow_incident
{
Url = "https://instance.service-now.com/change_request.do?SOAP",
Credentials = new NetworkCredential("Username", "Password")
};
var objRecord = new getRecords
{
number = "CH1****234"
};
var recordsResults = proxy.getRecords(objRecord);
//grdGetRecords is DataGridView.
getRecords is of type getRecordsResponseGetRecordsResult[]. I want to bind the recordsResults into grdGetRecords - DataGridView Control with headers.
first make the records result a public property:
public getRecordsResponseGetRecordsResult[] RecordsResults { get; set; }
Seconds bind in the xaml page throught a Binding in the ItemsSource:
<DataGridView ItemsSource="{Binding RecordsResults}" />
If you want dynamic update from the source or the view then be sure to put the Binding into TwoWay and make the property an ObservableCollection.
Hope it help!
I set the recordsResults to the DataSource property of grdGetRecords.
Code:
var recordsResults = proxy.getRecords(objRecord);
grdGetRecords.DataSource = recordsResults; // Code added for data binding
Now, the record has been binded to the DataGridView control.

C# inline sorting ObservableCollection does not update Data Binding

I have a ViewModel which contains an ObservableCollection<CustomKeyGroup<CustomItem>> property bound to a control in a View and the problem is that I want to sort this collection by a property in CustomKeyGroup<T>, without setting the ObservableCollection<...> object property (i.e. sort the collection inline):
public class MainViewModel : ViewModelBase {
... // data service etc code
private ObservableCollection<CustomKeyGroup<CustomItem>> _items = new ObservableCollection<CustomKeyGroup<CustomItem>>();
public ObservableCollection<CustomKeyGroup<CustomItem>> Items
{
get
{
return _items;
}
set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public void Sort(string _orderBy = null, bool _descending = true) {
if (string.IsNullOrEmpty(_orderBy) || this.Items.Count == 0) {
return;
}
var test = this.Items.ToList();
// bubble sort
try {
for (int i = test.Count - 1; i >= 0; i--) {
for (int j = 1; j <= i; j++) {
CustomKeyGroup<CustomItem> o1 = test[j - 1];
CustomKeyGroup<CustomItem> o2 = test[j];
bool move = false;
var order = typeof(CustomKeyGroup<CustomItem>).GetProperty(orderBy);
var t = order.GetValue(o1);
var t2 = order.GetValue(o2);
// sort comparisons depending on property
if (_descending) { // ascending
if (t.GetType() == typeof(int)) { // descending and int property
if ((int)t < (int)t2) {
move = true;
}
} else { // descending and string property
if (t.ToString().CompareTo(t2.ToString()) > 0) {
move = true;
}
}
} else { // ascending
if (t.GetType() == typeof(int)) { // ascending and int property
if ((int)t > (int)t2) {
move = true;
}
} else { // ascending and string property
if (t.ToString().CompareTo(t2.ToString()) < 0) {
move = true;
}
}
}
// swap elements
if (move) {
//this.Items.Move(j - 1, j); // "inline"
test[j] = o1;
test[j - 1] = o2;
}
}
}
// set property to raise property changed event
this.Items = new ObservableCollection<CustomKeyGroup<CustomItem>>(test);
} catch (Exception) {
Debug.WriteLine("Sorting error");
}
//RaisePropertyChanged("Items"); // "inline sort" raise property changed to update Data binding
Debug.WriteLine("Sorted complete");
}
... // get data from service, etc.
From the code above, the attempted inline sorts are commented out (as they do not update the control that databinds to it), and the manual setting of Items are left in (works, but if you scroll down the control and sort, it will take you back to the top - undesirable!).
Anyone have any idea how I can update the view/control using an inline sort option? I've also tried manually raising the RaisePropertyChanged event (specified in ObservableObject using the MVVMLight Toolkit) to no avail.
Note: Setting a breakpoint at the end of the try-catch reveals that the ObservableCollection<...> is indeed sorted, but the changes just do not reflect in the View! Even weirder is that the control (LongListSelector) has a JumpList bound to another property of CustomKeyGroup<T> and it successfully updates instantly!! If I tap on any of these items in the JumpList, the View correctly updates itself, revealing the sorted items... I then thought of setting the DataContext of the View after sorting, but that also does not solve the issue.
Thanks.
Adding my own answer here.
So following the comments from the original post, #piofusco points out that a View does not update when an ObservableCollection has only been sorted. Even manually changing the collection (hence, raising NotifyPropertyChanged or NotifyCollectionChanged) does not update it.
Searching around a little more, I decided I could make use of CollectionViewSource, which would do my sorting for me - without changing the collection itself (hence allowing the control to retain its current scroll position). To get it working, basically, add a new property to the ViewModel of type CollectionViewSource, add a SortDescription, set its Source and bind directly to that property (instead of the original ObservableCollection:
In ViewModel:
private CollectionViewSource _sortedCollection = new CollectionViewSource();
public CollectionViewSource SortedCollection {
get {
_sortedCollection.Source = this.Items; // Set source to our original ObservableCollection
return _sortedCollection;
}
set {
if (value != _sortedCollection) {
_sortedCollection = value;
RaiseNotifyPropertyChanged("SortedCollection"); // MVVMLight ObservableObject
}
}
}
View XAML (note the binding to Property.View):
<ListBox ItemsSource="{Binding SortedCollection.View}" ... />
And in your View code-behind, if you have a Sort button:
ViewModel _vm = this.DataContext as ViewModel;
viewModel.SortedCollection.SortDescriptions.Clear(); // Clear all
viewModel.SortedCollection.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Descending)); // Sort descending by "PropertyName"
And boom! Your sorted collection should update instantly in the View! Even better is that it retains our ObservableCollection functionality in that any updates to objects in the ObservableCollection will raise the NotifyPropertyChanged or NotifyCollectionChanged handlers, thereby updating the View (allowing for both sorting and updating of objects while retaining current scroll positions)!
Note: For those out there using a LongListSelector control, I wasn't able to get it to work, and with a little more internet-digging with I came across this post, which, discusses why LLS cannot bind to a CollectionViewSource.View without some modifications. So I ended up using a ListBox control instead. You can read about some of the differences here. For my task though, the ListBox will suffice.

Selected Item From ViewModel to Update using EF

I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.

Categories