Field as ObservableCollection gets cleared when property is cleared - c#

I'm relatively new to XAML / Xamarin and I'm running into something I'm hoping someone can help me clarify.
I know the title is misleading, but I couldn't put it any different.
I have the following property in my ViewModel:
private ObservableCollection<Schedule> _scheduleList;
public ObservableCollection<Schedule> ScheduleList
{
get
{
return _scheduleList;
}
set
{
value = _scheduleList;
}
}
Somewhere down the line, I do something like this:
private void DayFilter(string week)
{
try
{
var list = _scheduleList.Where(x => x.ScheduleDate.DayOfWeek.ToString() == week);
ObservableCollection<Schedule> newlist = new ObservableCollection<Schedule>(list);
ScheduleList.Clear(); // <- this line clears out _scheduleList as well
foreach (var item in newlist)
{
ScheduleList.Add(item);
}
}
catch (Exception ex)
{
throw ex;
}
}
Whenever ScheduleList.Clear() is called, it also clears out _scheduleList which is the private field.
I know this has something to do with the fact that this is ObservableCollection, but the requirement is that it should be, and I could not find a way to retain the value on _scheduleList, as I need this field populated throughout the lifetime of the application.
Is there away that the field _scheduleList does not get cleared out?

This is just how C# works
ScheduleList.Clear();
returns a reference to _scheduleList (that's what the public get does) and then calls Clear on it.
In your scenario, you probably need to maintain two completely separate copies of your data - the original, as well as one that you use for filtering/displaying the data.

Related

How can I bind a control to a List<string>?

Seems like a very basic MVVM question, but when it comes to Catel, I have issues doing it.
I have a property - registered as it should - which is a List, named Lines.
I bind it to a ListBox.
I also have a Button with a command adding a entry to Lines.
Lines is mapped to a model, and when I check the values of the model, I see it gets updated correctly when adding a value to Lines.
So everything seems to work, except that my view isn't updating when Lines is modified.
I tried to solve this by adding a RaisePropertyChanged("Lines") in Lines' setter, and in the command that adds a new value to Lines.
It gives something like this for the property:
[ViewModelToModel("MyModel", "Lines")]
public List<string> Lines
{
get { return GetValue<List<string>>(LinesProperty); }
set
{
SetValue(LinesProperty, value);
RaisePropertyChanged("Lines");
}
}
public static readonly PropertyData LinesProperty =
RegisterProperty("Lines", typeof(List<string>), null, (s, e) => {});
and this for the command (yes, I have AddLine = new Command(OnAddLineExecute); in the viewmodel's constructor):
public Command AddLine { get; private set; }
private async void OnAddLineExecute()
{
// this doesn't seem relevant, but since we're talking async and stuff, that may as well be the issue
if (!lineCountChecked && Lines.Count >= 4)
{
if (await messageService.Show(MainDialogs.LineCountCheck, "Lines count", MessageButton.OKCancel, MessageImage.Warning) != MessageResult.OK)
return;
else
lineCountChecked = true;
}
//
Lines.Add("New Line");
RaisePropertyChanged("Lines");
}
It's more than likely a very stupid mistake, but I can't get it. What did I miss? Thanks
You have 2 options to make this work:
1) RaisePropertyChanged(() => Lines) => will update the whole collection
2) Use ObservableCollection instead of List so the UI can actually respond to updates
I recommend 2.

Code First Databinding w/Filtered View

Good Afternoon,
I'm currently attempting to work with the Entity Framework DbContext/Code First inside a winforms application. My class, PrintQueueItem, is mapped to a view inside the database that only returns PrintQueueItem that have a PrintStatus equal to 'Pending'. This is displayed to the end-user via a DataGridView so they can view what's to be batch printed at the end of the day. Users are able to add instances of PrintQueueItem and this is displayed inside the DataGridView as well.
My issue arises when I attempt to cancel an item that's set to print. I set the PrintStatus to Cancelled and then run the ExecuteSqlCommand to update the database. After this, I try reload the information but the item is still displayed inside the DataGridView. I'm unsure why this is as when I get the count (via Local) after the information has been loaded again it has been decremented by one. Any idea why this would be as I thought the BindingList provides a 2-way sync for data-binding and the UI would be updated with the change in data?
PrintQueueItem (Model)
public class PrintQueueItem
{
public PrintQueueItem()
{
this.PrintQueueID = Guid.NewGuid();
this.PrintStatus = "Pending";
this.DateAdded = DateTime.Now;
}
public Guid PrintQueueID { get; set; }
public Guid DocumentID { get; set; }
public string PrintStatus { get; set; }
public DateTime DateAdded { get; set; }
public virtual Documentation Document { get; set; }
}
Initial Binding
this.printQueueBinding.DataSource = db.PrintQueue.Local.ToBindingList();
this.printQueueGridView.AutoGenerateColumns = false;
this.printQueueGridView.DataSource = printQueueBinding;
Update PrintStatus on PrintQueueItem and Reload
PrintQueueItem item = printQueueBinding.Current as PrintQueueItem;
if (item == null)
{
throw new ArgumentNullException("Could not obtain instance of selected 'PrintQueueItem'");
}
item.PrintStatus = "Cancelled";
this.db.Database.ExecuteSqlCommand("exec usp_PrintQueue_Update {0}, {1}",item.PrintQueueID, item.PrintStatus);
this.db.PrintQueue.Load();
Some Additional Notes
Instead of a call to the ExecuteSqlCommand, I tried calling SaveChanges but encountered an exception stating:
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded."
I'm assuming this is because the item is no longer found in the view when it's updated. Please let me know if this is not the case.
The reason for using a filtered view rather than filtering in the application is because BindingList doesn't implement IBindingListView and I'm unable to implement the BindingList.Filter method to filter the data displayed inside the DataGridView. Any suggestions on this are welcome as well.
Thank you for your help.
After a couple beers and some time to let the jumbled mess of logic I had in my brain clear out from earlier today; I began to think of what was actually happening and then it hit me that when I attempted to call SaveChanges a concurrency exception was happening.
I failed to pay attention to the type of exception that was raised and thus failed to properly handle it. I've since updated my code to the following so when a DbUpdateConcurrencyException occurs (expected, since the view no longer contains the record) the entities that experience the issue are refreshed from the database:
try
{
if (Program.YesNoQuestion(string.Format("Cancel print job for the selected document?", printQueueGridView.SelectedRows.Count)) != System.Windows.Forms.DialogResult.Yes)
{
return;
}
PrintQueueItem item = printQueueBinding.Current as PrintQueueItem;
if (item == null)
{
throw new ArgumentNullException("Could not obtain an instance of 'PrintQueueItem'");
}
item.PrintStatus = "Cancelled";
this.db.Database.ExecuteSqlCommand("exec usp_PrintQueue_Update {0}, {1}", item.PrintQueueID,
item.PrintStatus);
//this.db.PrintQueue.Load();
this.db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (DbEntityEntry entry in ex.Entries)
{
entry.Reload();
}
}
Please let me know if there are better ways to go about this as I'm always open to other ideas and always interested in doing things the proper way. Have a great night!

Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others

I am trying to get a StringElement's 'Value' to update in the UI when I set it after already setting up the DVC.
e.g:
public partial class TestDialog : DialogViewController
{
public TestDialog() : base (UITableViewStyle.Grouped, null)
{
var stringElement = new StringElement("Hola");
stringElement.Value = "0 Taps";
int tapCount = 0;
stringElement.Tapped += () => stringElement.Value = ++tapCount + " Taps";
Root = new RootElement("TestDialog")
{
new Section("First Section")
{
stringElement,
},
};
}
}
However the StringElement.Value is just a public field, and is only written to the UICell during initialization when Element.GetCell is called.
Why isn't it a property, with logic in the setter to update the UICell (like the majority of Elements, e.g. EntryElement.Value):
public string Value
{
get { return val; }
set
{
val = value;
if (entry != null)
entry.Text = value;
}
}
EDIT :
I made my own version of StringElement, derived from Element (basically just copied the source code from here verbatim)
I then changed it to take a class scoped reference to the cell created in GetCell, rather than function scoped. Then changed the Value field to a property:
public string Value
{
get { return val; }
set
{
val = value;
if (cell != null)
{
// (The below is copied direct from GetCell)
// The check is needed because the cell might have been recycled.
if (cell.DetailTextLabel != null)
cell.DetailTextLabel.Text = Value == null ? "" : Value;
}
}
}
It works in initial testing. However I am not sure on whether taking a reference to the cell is allowed, none of the other elements seem to do it (they only take references to control's placed within the cells). Is it possible that multiple 'live'* cell's are created based on the one MonoTouch.Dialog.Element instance?
*I say live to indicate cells currently part of the active UI. I did notice when navigating back to the dialog from a child dialog the GetCell method is invoked again and a new cell created based on the Element, but this is still a 1-1 between the element and the live cell.
For the main question:
Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others?
I've been through the code, and I don't think there's a consistent reason for use of either.
The Dialog project was not part of the MonoTouch project initially - I don't think Miguel knew how useful it was going to turn out when he started wrote and grew it - I think he was more focussed on writing other apps like TweetStation at the time.
I know of several people (including me!) who have branched the code and adapted it for their purposes. I would guess at some future point Xamarin might write a 2.0 version with stricter coding standards.
Taking references to live cells
For limited use you can do this... but in general don't.
The idea of the table view is that cells get reused when the user scrolls up and down - especially in order to save memory and ui resources. Because of this is a long list, multiple elements might get references to the same cell.
If you do want to cache a cell reference then you probably should override GetCell() so that it never tries to reuse existing cells (never calls DequeueReusableCell)
Alternatively, you could try to change some code in the base Element class in order to find out if the Element has a current attached cell - this is what CurrentAttachedCell does in my branch of Dialog https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Dialog/Dialog/Elements/Element.cs (but that branch has other added functions and dependencies so you probably won't want to use it for this current work!)

Get a RETURN from a Invoke method

I am trying to read value from a listbox item that is on another thread.
I tried to make a new method to run the invoke command, I can manage to send a command to the listbox like add via the invoke method but i cant seem to get a response, i cant seem to get the value of the item, i have tried a few ways, once i change it from a void to a string things start to get hairy...
thread t1 = new thread(thethread)
t1.start()
public void thethread()
{
string text = readListBoxSelected(listBox1) + " lala" ;
}
public static string readListBoxSelected(ListBox listbox)
{
if (listbox.InvokeRequired)
{
return (string)listbox.Invoke(
new Func<String>(() => readListBoxSelected(listbox))
);
}
else
{
string varText = listbox.SelectedValue.ToString();
return varText;
}
}
Above is a example of what i am trying to do.
Here is the error:
System.NullReferenceException was
unhandled by user code
Message=Object reference not set to an
instance of an object.
Source=** StackTrace:
at **.Form1.readListBoxSelected(ListBox listbox) in e:\documents and
settings\scott\my documents\visual
studio
2010\Projects*****\Form1.cs:line
133
at ***.Form1.<>c_DisplayClass5.b_3()
in e:\documents and settings\scott\my
documents\visual studio
2010\Projects******\Form1.cs:line
127 InnerException:
I imagine what is wrong is exactly what it says "Object reference not set to an instance of an object"....... All my variables seem to be declared as fair as i am aware, how can i correct this??
I get the feeling i am going about the entire thing wrongly.... 0_o
Thanks in Advance,
Scott
Try This
public static string readListBoxSelected(ListBox listbox)
{
if (listbox.InvokeRequired)
{
return (string)listbox.Invoke(
new Func<String>(() => readListBoxSelected(listbox))
);
}
else
{
if(istbox.SelectedValue != null)
return listbox.SelectedValue.ToString();
else
return String.Empty
}
}
Code looks fine, the problem seems on the SelectedValue, is it null. ???
Thanks guys,
You where correct, Problem was it was returning a null value..
I was so sure that i was selecting the item correctly i never thought it could be the problem.
Turns out the problem was two things:
1)
The way i was selecting item, i was using listbox.Selecteditem = 1 , now if i use listbox.setSelected(1,true) all is good :)
and
2)
The way i was getting the items text was wrong, listbox.SelectedValue is nothing, it dosnt do what we all imagine it to do... the call i need was listbox.Text .........
public static string readListBoxSelected(ListBox listbox)
{
if (listbox.InvokeRequired)
{
return (string)listbox.Invoke(
new Func<String>(() => readListBoxSelected(listbox))
);
}
else if(listbox.Text != null)
{
return listbox.Text.ToString();
}
else
return String.Empty;
}
public void selectListBoxItem(ListBox listbox, int num)
{
Invoke(new MethodInvoker(delegate { listbox.SetSelected(num,true); }));
}
I must say this is the most anoying thing i have ever done... Everything requires i write a delegate / invoke method for it... Everything... woudlnt something so common be supported by .net on the fly....
Seems a waist of time to write individual delegates for EVERYTHING...
Thanks guys all is working now, yesterday i couldn't foresee me getting to this point,
Overall problem was Wrong Calls, the invoke was all fine :)
Scott
EDIT:
ok it was returning NULL simply because listbox.SelectedValue isnt really the call im after to read the selectedvalue (you would think it was), if i change it to listbox1.text all works fine.... rather silly this .net object oriented stuff if i do say so....
I must say what a joke... thats kindly destroyed my faith in object oriented programming..
I understand this is not a discussion fourm but honestly the call SelectedValue.toString() should do what we all think it will do.... nope we need to use .Text to get what we require 0_o.........

Correct way to implement web part personalisation for listboxes

Trying to work out this whole web part personalisation, and trying to implement it for a list box.
Well the end result will be two list boxes, with interchangeable values (ie, a value will only exist in one of the listboxes)
But I can't maintain the datasource for it. So maybe I'm going about it wrong?
This is what I have for a test H2 tag on the page
[Personalizable(PersonalizationScope.User)]
public string LabelText {
get { return h2Test.InnerText; }
set { h2Test.InnerText = value; }
}
And it works fine, if I have a textbox and use it to change the value of LabelText, then when I close the browser it automagically persists the change.
So I thought, ok, then maybe the same will work with a list box
[Personalizable(PersonalizationScope.User)]
public DomainList Domains {
get { return (DomainList)lstBxDomains.DataSource; }
set {
lstBxDomains.DataSource = value;
lstBxDomains.DataBind();
}
}
Where DomainList is just a class which extends List, and Domain is just a three field class, int, string, string.
But it doesn't, so is this too complicated for the webpart personalisation automagican, or have i just implement it wrongly (Which is more than likely)
This is my event handler to remove the items from the list:
protected void btnRemDomain_Click(object sender, EventArgs e) {
if (IsPostBack && lstBxDomains.SelectedIndex > -1) {
for (int i = 0; i < lstBxDomains.Items.Count; i++) {
if (lstBxDomains.Items[i].Selected) {
Domains.Remove(Domains.Find(d => d.ID.ToString() == lstBxDomains.Items[i].Value));
}
}
Domains = Domains;
}
}
The Domains=Domains; line is in there to see if explicitly setting the value made a difference (as Removing doesn't acutally reset the value of the field), but it doesn't. I've also tried creating a new local DomainList setting it to the global one, and then doing the remove/find on it, and then setting the local one to the global. But not working either.
I have managed to resolve this by using WebPart.SetPersonalizationDirty(this); in the set accessor of Domains, but would someone mind confirming if this is an appropriate way to do it?

Categories

Resources