Navigation Helper SaveState / LoadState works incorrect - c#

First, I read a list of entries from a database and display it in a ListView. When I leave the page to show details of an entry, then go back to the list, everything is ok.
Next, I open another page to add one entry to the database.
Go back to the list, reading from database shows me the correct count.
When I go to display one detail, the correct count is stored in SaveState.
Go back to the list, LoadState give the wrong count. It's the former state.
Display other details and go back now works with the old list and do not show me the added entry.
This is my code:
private void getList()
{
memoList = new List<MemoItem>();
db.loadHistory(ref memoList);
DelButton.IsEnabled = false;
System.Diagnostics.Debug.WriteLine("GetList, memoList[{0}]", memoList.Count);
}
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
{
if (memoList != null)
{
e.PageState["MemoItem"] = memoList;
if (memoSelected > -1)
e.PageState["memoSelected"] = memoSelected;
System.Diagnostics.Debug.WriteLine("SaveState, memoList[{0}]", memoList.Count);
}
}
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
if (e.PageState != null)
{
if (e.PageState.ContainsKey("MemoItem"))
{
memoList = (List<MemoItem>)e.PageState["MemoItem"];
System.Diagnostics.Debug.WriteLine("LoadState, memoList[{0}]", memoList.Count);
if (e.PageState.ContainsKey("memoSelected"))
memoSelected = (int)e.PageState["memoSelected"];
MemoListView.ItemsSource = memoList;
MemoListView.Visibility = Visibility.Visible;
}
else
{
getList();
showList();
}
}
}
Here are the Systems.Diagnostic outputs with comments in ():
GetList, memoList[8] (first time loaded)
SaveState, memoList[8] (leave the list to display details for one entry)
LoadState, memoList[8] (come back to the ListView)
SaveState, memoList[8] (leave the list to another page and add one entry)
GetList, memoList[9] (back to the list, read the correct entry count from database)
SaveState, memoList[9] (leave the ListView to display details for one entry)
LoadState, memoList[8] (come back to the ListView loads the wrong old list)
SaveState, memoList[8] (and works with the old list...)
LoadState, memoList[8] (...)
Remark: I can't call GetList from database every time, because I have to preserve checkmarks in the list which are not contained in the database.
What is wrong in my code? How can I resolve this problem? How to invalidate the StateEvent data after availability of a new list from database?

The problem is solved now. It was actually a consequence of the back stack manipulation.
I have four basic pages in my program, for which a GoBack makes sense. But I do not want to go back deeper. (I do not think anyone wants to go back 20 or more steps, but the seemingly unlimited BackStack would allow that.The required memory consumption is not negligible.)
In order to clear the BackStack at selecting one of the basic pages, I had this:
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
rootFrame.Navigate(pageType, rootFrame);
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 1)
{
rootFrame.BackStack.RemoveAt(0);
}
}
}
}
}
}
That worked fine until I came across the problem with SaveState / Load State.
Now I first empty the BackStack and then navigate to the target page. That works.
private void NavButton_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if (b != null && b.Tag != null)
{
Type pageType = Type.GetType(b.Tag.ToString());
if (pageType != null)
{
if (rootFrame.CurrentSourcePageType != pageType)
{
// No goBack for Basic Pages
if (testBasicPage(pageType))
{
while (rootFrame.BackStackDepth > 0)
{
rootFrame.BackStack.RemoveAt(0);
}
}
rootFrame.Navigate(pageType, rootFrame);
}
}
}
}

Related

MVVM Observable collection issue

I am creating A WPF application and am currently having issues with updating the visuals for a particular instance.
I have a ViewModel.Textlines which I am trying to change one element within that. It works and behaves fine from what I gather.
I am using Remove and Insert and it doesnt work. I use breakpoints and find out it all seems to work and the element has indeed been swapped out and when I check the OC it will show the values I wanted it to have and OnPropertyChanged Has been called after but it fails to update that. It might occure because the value is not a 'new' MFD_textline as I am just assigning it via =
Below is the code I am using
private void Controller_UpdateEvent(object sender, UpdateEventArgs e)
{
if(e.SystemName == "ALE47")
{
if(e.Values.ContainsKey("PageIndex") && e.Values.ContainsKey("Value") && e.Values.ContainsKey("TextLine"))
{
int pageIndex = (int)e.Values["PageIndex"].NewValue;
int value = (int)e.Values["Value"].NewValue;
textLine = new MFD_Textline();
textLine = (MFD_Textline)e.Values["TextLine"].NewValue;
ViewModel.TextLines.RemoveAt(value);
ViewModel.TextLines.Insert(value, textLine);
}
}
}
public void TextLineChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// when the textlines obserevable collection is changed (added to or removed) force a property changed to update the view
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (TextLines.Count == 11)
{
OnPropertyChanged("textLines");
}
}
}
private ObservableCollection<MFD_Textline> textLines;
public ObservableCollection<MFD_Textline> TextLines
{
get { return textLines; }
set
{
textLines = value;
OnPropertyChanged("textlines");
}
}
Please let me know if there are any questions or if I have not quite explained it right.
This call is wrong OnPropertyChanged("textlines");
You should pass the Property's name, not the backing field's
OnPropertyChanged("TextLines");

C1RichTextBox with custom copy/paste behavior

When using C1RichTextBox in Silverlight 5 with IE 10, I am facing two major issues:
During a clipboard paste operation, how can I detect if the content was copied from another C1RichTextBox in my Silverlight application or from an external application? From external applications, only text should be pasted without formatting.
Copy/Pasting large inline images from one C1RichTextBox to another does not work. The <img> elements have the image content stored in their data URL. If the image becomes too large (approx 1MB), the src attribute is dropped when copied to the clipboard.
The solution should:
Not have side-effects on the global clipboard or on the editing behavior of the C1RichTextBox.
Be robust against changes to the C1RichTextBox implementation.
Not have to modify/parse/analyze the HTML document in the clipboard.
It took me a while to figure all this (an a lot more...) out and I am happy to share with anyone who has to deal with these issues.
I am using a derived class to solve the issues
public class C1RichTextBoxExt : C1RichTextBox
{
1. Pasting from external application with text-only
The solution is simple in theory: Get a hold of the HTML after text from within the RichTextBox was copied/cut to the clipboard. When pasting, compare the current HTML in the clipboard with what was last copied. Because the clipboard in ComponentOne is global, the content changes if a Copy/Cut was done in another application and thus the HTML will be different.
To remember the last copied HTML, we use a static member inside C1RichTextBoxExt:
private static string _clipboardHtml;
The bad news is: The C1RichTextBox.ClipboardCopy() etc. methods are not virtual. The good news is: The keyboard shortcuts for Copy/Cut/Paste which call these methods can be disabled, e.g. in the constructor:
RemoveShortcut(ModifierKeys.Control, Key.C);
RemoveShortcut(ModifierKeys.Control, Key.Insert);
RemoveShortcut(ModifierKeys.Control, Key.V);
RemoveShortcut(ModifierKeys.Shift , Key.Insert);
RemoveShortcut(ModifierKeys.Control, Key.X);
RemoveShortcut(ModifierKeys.Shift , Key.Delete);
Now that the methods C1RichTextBox.ClipboardCopy() etc. are no longer called we can wire up our own version by overriding OnKeyDown:
protected override void OnKeyDown(KeyEventArgs e)
{
if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.C)) { ClipboardCopy(); }
else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.Insert)) { ClipboardCopy(); }
else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.V)) { ClipboardPaste(); }
else if ((Keyboard.Modifiers == ModifierKeys.Control) && (e.Key == Key.X)) { ClipboardCut(); }
else if ((Keyboard.Modifiers == ModifierKeys.Shift) && (e.Key == Key.Insert)) { ClipboardPaste(); }
else if ((Keyboard.Modifiers == ModifierKeys.Shift) && (e.Key == Key.Delete)) { ClipboardCut(); }
else
{
// default behaviour
base.OnKeyDown(e);
return;
}
e.Handled = true; // base class should not fire KeyDown event
}
To not accidentally call the base class methods, I am overwriting them (see below, using new modifier). The ClipboardCopy() method just calls the base class and afterwards stores the clipboard HTML. A small pitfall here was to use Dispatcher.BeginInvoke() since the C1RichTextBox.ClipboardCopy() stores the selected text in the clipboard inside a Dispatcher.BeginInvoke() invocation. So the content will only be available after the dispatcher had a chance to run the action provided by C1RichTextBox.
new public void ClipboardCopy()
{
base.ClipboardCopy();
Dispatcher.BeginInvoke(() =>
{
_clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
});
}
The ClipboardCut method is very similar:
new public void ClipboardCut()
{
base.ClipboardCut();
Dispatcher.BeginInvoke(() =>
{
_clipboardHtml = C1.Silverlight.Clipboard.GetHtmlData();
});
}
The ClipboardPaste method can now detect if pasting external data. Pasting text only is no so straightforward. I came up with the idea to replace the current clipboard content with the text-only representation of the clipboard. After pasting is done, the clipboard should be restored so the content can be pasted again in other applications. This also has to be done within a Dispatcher.BeginInvoke() since the base class method C1RichTextBox.ClipboardPaste() performs the paste operation in a delayed action as well.
new public void ClipboardPaste()
{
// If the text in the global clipboard matches the text stored in _clipboardText it is
// assumed that the HTML in the C1 clipboard is still valid
// (no other Copy was made by the user).
string current = C1.Silverlight.Clipboard.GetHtmlData();
if(current == _clipboardHtml)
{
// text is the same -> Let base class paste HTML
base.ClipboardPaste();
}
else
{
// let base class paste text only
string text = C1.Silverlight.Clipboard.GetTextData();
C1.Silverlight.Clipboard.SetData(text);
base.ClipboardPaste();
Dispatcher.BeginInvoke(() =>
{
// restore clipboard
C1.Silverlight.Clipboard.SetData(current);
});
}
}
2. Copy/Pasting large inline images
The idea here is similar: Remember the images when copied, put them back in during paste.
So first we need to store where which image is in the document:
private static List<C1TextElement> _clipboardImages;
private static int _imageCounter;
(The use of _imageCounter will be explained later...)
Then, before Copy/Cut is executed, we search for all images:
new public void ClipboardCopy()
{
_clipboardImages = FindImages(Selection);
base.ClipboardCopy();
// ... as posted above
}
and similar:
new public void ClipboardCut()
{
_clipboardImages = FindImages(Selection);
base.ClipboardCut();
// ... as posted above
}
The methods to find the images are:
private List<BitmapImage> FindImages(C1TextRange selection = null)
{
var result = new List<BitmapImage>();
if (selection == null)
{
// Document Contains all elements at the document level.
foreach (C1TextElement elem in Document)
{
FindImagesRecursive(elem, result);
}
}
else
{
// Selection contains all (selected) elements -> no need to search recursively
foreach (C1TextElement elem in selection.ContainedElements)
{
if (elem is C1InlineUIContainer)
{
FindImage(elem as C1InlineUIContainer, result);
}
}
}
return result;
}
private void FindImagesRecursive(C1TextElement elem, List<BitmapImage> list)
{
if (elem is C1Paragraph)
{
var para = (C1Paragraph)elem;
foreach (C1Inline inl in para.Inlines)
{
FindImagesRecursive(inl, list);
}
}
else if (elem is C1Span)
{
var span = (C1Span)elem;
foreach (C1Inline child in span.Inlines)
{
FindImagesRecursive(child, list);
}
}
else if (elem is C1InlineUIContainer)
{
FindImage(elem as C1InlineUIContainer, list);
}
}
private void FindImage(C1InlineUIContainer container, List<BitmapImage> list)
{
if (container.Content is BitmapImage)
{
list.Add(container.Content as BitmapImage);
}
}
I won't go into details about the above methods, they are pretty straightforward if you analyze the structure of C1RichTextBox.Document.
Now, how do we restore the images? The best I found is to use the ConvertingHtmlNode event of the C1RichTextBox.HtmlFilter. This event is fired every time a HTML node is converted into a C1TextElement. We subscribe to it in the constructor:
HtmlFilter.ConvertingHtmlNode += new EventHandler<ConvertingHtmlNodeEventArgs>(HtmlFilter_ConvertingHtmlNode);
and implement it like this:
void HtmlFilter_ConvertingHtmlNode(object sender, ConvertingHtmlNodeEventArgs e)
{
if (e.HtmlNode is C1HtmlElement)
{
var elem = e.HtmlNode as C1HtmlElement;
if (elem.Name.ToLower() == "img" && _clipboardImages != null && _clipboardImages.Count > _imageCounter)
{
if (!elem.Attributes.ContainsKey("src")) // key comparison is not case sensitive
{
e.Parent.Children.Add(_clipboardImages[_imageCounter].Clone());
e.Handled = true;
}
_imageCounter++;
}
}
}
So for each HTML element node with the name "img" we check if the "src" attribute is missing. If so, we add the next stored image instead and tell the event source that the event is now handled (for this HTML node) by setting e.Handled = true;
Which image is the "next" image is determined by the _imageCounter field which is incremented for each visited "img" element.
The _imageCounter field must be reset when ClipboardPaste() is invoked, so we do:
new public void ClipboardPaste()
{
_imageCounter = 0;
string current = C1.Silverlight.Clipboard.GetHtmlData();
// ... as posted above
}
Conclusion
If you copy/paste (no pun intended...) all code blocks posted above together, you should end up with a solution which has no side-effects (at least none known to the author as of today), is robust against changes and does almost no HTML processing.

save and load Listbox Items locally and pass them to other pages

I am currently working on Windows Store App in c#.
Now,
I am having a list box 'Listbox1' which gets its items on a button click event from a text box 'tasks', and have selected Items delete property on other button click event.
private void add_Click(object sender, RoutedEventArgs e)
{
string t;
t = tasks.Text;
if (t != "")
{
Listbox1.Items.Add(t);
}
else
{
var a = new MessageDialog("Please Enter the Task First");
a.Commands.Add(new UICommand("Ok"));
a.ShowAsync();
}
tasks.Text = "";
}
private void del_Click(object sender, RoutedEventArgs e)
{
for (int p = 0; p < Listbox1.SelectedItems.Count; p++)
{
Listbox1.Items.Remove(Listbox1.SelectedItems[p].ToString());
p--;
}
}
Now I want to save this list into local application storage, after user complete the changes (on a button click event perhaps).
And also to send all Listbox Items to another page(s).
I am not much a coder, I design things.
Please guide me by sample or reference.
Thank you in advance :)
If you have already stored the data to local storage, you could just read it in the OnNavigatedTo override of the other page. Otherwise, use the navigation parameter: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/8cb42356-82bc-4d77-9bbc-ae186990cfd5/passing-parameters-during-navigation-in-windows-8
Edit: I am not sure whether you also need some information about local storage. This is easy: Windows.Storage.ApplicationData.Current.LocalSettings has a property called Values, which is a Dictionary you can write your settings to. Have a look at http://msdn.microsoft.com/en-us/library/windows/apps/hh700361.aspx
Edit: Try something like this code to store your list.
// Try to get the old stuff from local storage.
object oldData = null;
ApplicationDataContainer settings = ApplicationData.Current.LocalSettings;
bool isFound = settings.Values.TryGetValue("List", out oldData);
// Save a list to local storage. (You cannot store the list directly, because it is not
// serialisable, so we use the detours via an array.)
List<string> newData = new List<string>(new string[] { "test", "blah", "blubb" });
settings.Values["List"] = newData.ToArray();
// Test whether the saved list contains the expected data.
Debug.Assert(!isFound || Enumerable.SequenceEqual((string[]) oldData, newData));
Note, this is only demo code for testing - it does not make real sense...
Edit: One advice: Do not persist the list in your click handlers as this will become extremely slow as the list grows. I would load and save the list in the Navigation handlers, i.e. add something like
protected override void OnNavigatedTo(NavigationEventArgs e) {
base.OnNavigatedTo(e);
if (this.ListBox1.ItemsSource == null) {
object list;
if (ApplicationData.Current.LocalSettings.Values.TryGetValue("List", out list)) {
this.ListBox1.ItemsSource = new List<string>((string[]) list);
} else {
this.ListBox1.ItemsSource = new List<string>();
}
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
if (this.ListBox1.ItemsSource != null) {
ApplicationData.Current.LocalSettings.Values["List"] = this.ListBox1.ItemsSource.ToArray();
}
base.OnNavigatedFrom(e);
}
Here is very nice simple example on SQLite DataBase Use in winRT app Development. Look at it and you will know how you can store your Data on the Local Machine. I learned Basic code from this example.
http://blogs.msdn.com/b/robertgreen/archive/2012/11/13/using-sqlite-in-windows-store-apps.aspx
Now, for ease of navigation let me suggest you a flow for this portion of your app.
take one ObservableCollection<> of string and store values of
that textBox into this ObservationCollection with onClick() and then
refer that ObservableCollection<String> to the ItemsList of the
listBox.
now at the time you need to send your Data to the next page, make one parameterised constructor of next page and pass that ObservableCollection<String> as it's parameter.
Now you can access those Data in your constructor and can use as however you want.
Hope this will help..

Multiple event handler firings, why?

I have a tough time trying to solve this problem. I have been at for 3 hours, and still I couldn't find out why it is doing this. Here is the code:
private void Catagory_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int selectedCategoryId = categoryIdList[categoryListBox.SelectedIndex];
client.GetItemsAsync(selectedCategoryId);
client.GetItemsCompleted +=
new EventHandler<GetItemsCompletedEventArgs>(client_GetItemsCompleted);
}
void client_GetItemsCompleted(object sender, GetItemsCompletedEventArgs e)
{
itemIdList.Clear();
itemNameList.Clear();
itemNumberList.Clear();
itemDisplayList.Clear(); //Clears the Display List Items
if (e.Error == null)
{
itemIdList = e.ItemIDList;
itemNumberList = e.itemNumber;
itemNameList = e.Result;
for (int i = 0; i < itemIdList.Count; i++)
{
itemDisplayList.Add(new ItemDisplay { itemNumber = itemNumberList[i], itemName = itemNameList[i] });
}
//Populating the listbox controll with the itemDisplaylist...
Items.ItemsSource = itemDisplayList;
}
else
{
MessageBox.Show("Problem in getting the items list.");
}
}
When I change the category the first time it works perfectly. By perfectly, I mean that it calls the function GetItemsAsync(selectedCategoryId) and grabs the results and calls the event handler client_GetItemsCompleted() and the inner working of the event handler works as it is supposed to, it sets the lists with the proper data and displays the itemNumber and the itemName in the list box . But, when I change the category again to get different items, it doesn't work properly, what it's doing is that it clears the lists and populates the lists as it is supposed to, runs the for loop and populates the listBox called Items but for some reason it goes to the top of the function again and empties all the lists. Please tell my why it is executing the function again? And when I choose another category again, it executes the event handler 3 times and then 4 times and so on. Wnow why it is doing this?
Everytime this is executed:
client.GetItemsCompleted +=
You add a subscriber to the event, so the second time it will fire twice (the third time triple times, etc..).
Either unsubscrice ( -= ) in the completed method:
void client_GetItemsCompleted(object sender, GetItemsCompletedEventArgs e)
{
try {
/* .... */
}
finally {
client.GetItemsCompleted -=
new EventHandler<GetItemsCompletedEventArgs>(client_GetItemsCompleted);
}
}
or initiate the client object before every call.
var client = new ...();
client.GetItemsAsync(selectedCategoryId);
client.GetItemsCompleted +=
new EventHandler<GetItemsCompletedEventArgs>(client_GetItemsCompleted);

Context.Items clears during page refresh/transfer

I am working on a class project and I've run into a problem I can't figure out. I have a feeling it's actually pretty easy, but I've been working on stuff so long I can't think straight anymore.
I have a login page that allows a user to login and pass 2 data items to the next page using Context.Items and Server.Transfer. Here is the code snippet:
Context.Items["preferred"] = true;
Context.Items["pageNum"] = 1;
Server.Transfer("ProductsShelf.aspx");
On the "ProductsShelf" page I can access those two items and use the data like so:
pageNumber = (int)Context.Items["pageNum"];
I am then using a switch-statement with pageNumber to display certain information:
switch (pageNumber)
{
case 1:
imgProd.ImageUrl = "assets/laptop.bmp";
lbl_Name.Text = "Laptop";
lbl_desc.Text = "This is a cheap laptop!";
lbl_price.Text = "199.99";
break;
}
Obviously there's other entries I'm omitting. What I want to do is click a next or previous button and use the event to change the Context.Items["pageNum"] data so the Page_Load() event uses different data in the switch-statement. Hope that makes sense. Here is one of the button click events:
protected void btn_Prev_Click(object sender, EventArgs e)
{
if (pageNumber == 1 || pageNumber == 2)
{
Context.Items["pageNum"] = 1;
}
else if (pageNumber == 3)
{
Context.Items["pageNum"] = 2;
}
Context.Items["preferred"] = preferredCustomer;
Server.Transfer("ProductsShelf.aspx");
}
The problem is that before the button click event fires, the form posts and clears the Context.Items and pageNumber values. This means that the button event if-statements never fire and it results in:
pageNumber = (int)Context.Items["pageNum"];
Being null, throwing an exception and making me very sad. So my question is, how can I go about retaining the values? Should I switch to Response.Redirect and have something like ?page=1 in the URL? Or will that clear too when the form posts? Hopefully I'm not doing this completely wrong.
If TL;DR, here's a quick summary:
Context.Items has 2 values passed with Server.Transfer
These values determine what's shown on the next page
The form clears Context.Items and variables before button click event fires
The values are null, the if-statement doesn't run, and the app throws an exception
Question: how should I go about retaining those values?
Thanks a lot. :)
HttpContext items can be used within one request only - it will be recreated for next request so your values are bound to lose. You should use view-state to preserve data across post-backs. In page load, you should check if data exists in context and then copy it to view-state. Then in button click events, you can read the data from view-state, put into the context items and do server.transfer.
Here's simple sample code:
private int PageNumber
{
get
{
var value = ViewState["pageNum"];
return null == value? 1: (int)value;
}
set
{
ViewState["pageNum"] = value;
}
}
private bool IsPreferredCustomer
{
get
{
var value = ViewState["preferred"];
return null == value? false: (bool)value;
}
set
{
ViewState["preferred"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
var preferred = Context.Items["preferred"];
if (null != preferred)
{
IsPreferredCustomer = (bool)preferred;
}
var pageNum = Context.Items["pageNum"];
if (null != pageNum )
{
PageNumber = (int)Context.Items["pageNum "];
}
}
Use the same PageNumber property in event code.

Categories

Resources