I have an initial XML file stored in my phone which is accessed to select a number of elements. Each element has a corresponding online XML file which I need to access to get more information.
string name, photo;
foreach (int num in combi)
{
no = xElem.Descendants("employee").ElementAt(num).Descendants("no").First().Value;
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler
(Info_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri
("http://example.com/" + name));
list.Add(new Person(no, name, photo);
}
void Info_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null) return;
XElement xml = XElement.Parse(e.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
}
However, it seems that the list.Add goes first before the XML is downloaded resulting into a list with empty values for name and photo. I confirmed this by placing MessageBox in both the foreach loop and Info_DownloadStringCompleted. Is there a better way of doing this?
To add to #MikkoVitala's answer, better for you to use ObservableCollection for the list.
ObservableCollection has built-in mechanism to notify UI to refresh whenever item added to or removed from collection. So it doesn't matter when you bind list to ListBox, the ListBox will always display up to date member of list :
string name, photo;
ObservableCollection<Person> list = new ObservableCollection<Person>();
foreach (int num in combi)
{
.....
}
MyListBox.ItemsSource = list;
Problem with your solution is that in your iteration over combi you'll try to use fields name and photo which are only assigned to in your eventhandler Info_DownloadStringCompleted.
Since DownloadStringAsync is, well... asynchronous, you'll exit foreach before DownloadStringCompleted event is raised, your eventhandler called and ultimately your fields been assigned to.
You can correct this by moving your list-add-logic to be executed after event has been raised.
....
wc.DownloadStringCompleted += (sender, args) =>
{
// Code from your Info_DownloadStringCompleted event handler
if (args.Error != null)
return;
XElement xml = XElement.Parse(args.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
// Now your fields are assigned and you can do-what-ever-with-'em
list.Add(new Person(no, name, photo);
};
....
If you like you can use extension methods to utilize async/await keywords and make it simpler and easier to read and understand. See SO answer here https://stackoverflow.com/a/13174270/1061668
Then above becomes (remember to mark method async)
....
// Note that exception is not handled in this example....
xml = await wc.DownloadStringTask(new Uri("http://example.com/" + name));
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
list.Add(new Person(no, name, photo);
....
Related
Using WPF C#
Have a TimePicker from Xceed.WPF.Toolkit called TimePicker, want to be able to filter the files that are loaded to a listbox based on the last write time of a file
Question: I would like to limit the files listed based on the logTime variable using Linq and where the files have to match the logTime
Code for population of list box currently
private void LoadLogsNoDate(string ldate, string ext)
{
string[] logs = Directory.GetFiles(logPath + ldate, ext);
InitializeComponent();
logList = new ObservableCollection<String>();
logList.Clear();
lbLogs.ItemsSource = logList;
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
logList.Add(s); // <- adds the items it finds
}
DataContext = this;
}
Need to set the TimePicker value to a logTime variable then use the logTime to filter the list of items that are displayed
Successfully have used this code to get the LastWriteAccess time just need some assistance putting it all together properly
public static void Times(string sFile)
{
FileInfo info = new FileInfo(sFile);
DateTime time = info.LastWriteTime;
string s = time.ToString("HH:mm tt");
Console.WriteLine("Last Access: " + s);
}
First off, you will want to associate the time with the string representing the log file. Instead of an ObservableCollection you'll have an ObservableCollection. Log would be defined as:
public class Log
{
String LogName;
DateTime LogWriteTime;
}
Creating the collection would call your other function (modified to return the read time):
foreach (string logName in logs)
{
string s = logName.Substring(logName.IndexOf(ldate) + ldate.Length + 1);
int extPos = s.LastIndexOf("."); // <- finds the extension
s = s.Substring(0, extPos); // <- removes the extension
s = s.ToUpper(); // <- converts to uppercase
Log newItem = new Log();
newItem.LogName = s;
newItem.LogWriteTime = GetFileAccessTime(s)
logList.Add(s); // <- adds the items it finds
}
public DateTime GetFileAccessTime(string sFile)
{
FileInfo info = new FileInfo(sFile);
return info.LastWriteTime;
}
Now that we have our time stored off, I'm going to assume your TimePicker's SelectedValue property is bound to FilterTime. There are two ways to approach the problem of filtering:
Bind your view to a separate IEnumerable FilteredLogs and do the following in the setter of the FilterTime variable:
FilterdLogs = logList.Where(l => l.LogWriteTime >= FilterTime);
Use a CollectionViewSource. This method is awesome! First, create a CollectionViewSource property called LogsSource. Change your XAML to bind to this instead of the old ObservableCollection.
<ListBox ItemsSource="{Binding FilteredLogs.View}"/>
Now, in whatever you have for an Init function (constructor, whatever calls LoadLogsNoData, etc.) write:
FilteredLogs = new CollectionViewSource();
FilteredLogs.Source = logList;
FilteredLogs.Filter += CheckAccessTime;
This sets up a new CollectionViewSource that points to your logList collection, and uses the CheckAccessTime function to determine if a particular log entry should be included in the "View" property (which you previously bound to).
The CheckAccessTime function will look like:
private void CheckAccessTime(object sender, FilterEventArgs e)
{
Log logEntry = e.Item as Log;
if (logEntry != null)
{
if (logEntry.LogWriteTime >= FilterTime)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
}
Finally, you need to refresh the filter anytime the selection changes. So, in the setter of FilterTime write:
FilteredLogs.View.Refresh();
The second option is, in my opinion, a much cleaner way of accomplishing the task, though it may be a bit more confusing at first. Let me me know if I can clarify anything!
This blog post was greatly helpful in researching the second method: http://uicraftsman.com/blog/2010/10/27/filtering-data-using-collectionviewsource/
MSDN for the filter event: MSDN
I want to add submenu into the main menu in runtime. I have looked at other post
Adding to strip menu at run time but I don't understand what I'm missing here because it only populates one item, but I have three xml files in the folder. Below is the code. testsuite paramenter contains xml files.
public void LoadTestSuiteMenuStrip(string[] testsuite)
{
try
{
foreach (var temp in testsuite)
{
int max = temp.Length-1;
while (temp[max] != '\\')
max--;
max++;
//remove the folder path and take only xml file name
string name = temp.Substring(max, temp.Length - max);
ToolStripMenuItem subItem = new ToolStripMenuItem(name);
//subItem.DisplayStyle = ToolStripItemDisplayStyle.Text;
//subItem .Text = name;
//subItem .Name = name;
//subItem.Tag = name;
subItem.Click += new EventHandler(testSuiteToolstrip_Click);
testsuiteToolStripMenuItem.DropDownItems.Add(subItem);
}
}
catch (Exception error)
{
}
}
thanks
Make sure that you access GUI from main thread. If you want to add items from other thread, instead of
LoadTestSuiteMenuStrip(args);
use
Invoke(new Action<string[]>(LoadTestSuiteMenuStrip), new object[] { args });
I actually display on my Listbox this list of item that i retrive from XML . When I click on an Item i am going back to the same method and creating a new list to display with different items.
I am wondering why it's not clearing the previous list.
This is the code I use, I can't figure this out ..
if (e.Error == null)
{
// Retrieving the subfolders
XDocument xdoc = XDocument.Parse(e.Result, LoadOptions.None);
XNamespace aNamespace = XNamespace.Get("http://schemas.datacontract.org/2004/07/System.IO");
var folders = from query in xdoc.Descendants(aNamespace.GetName("DirectoryInfo"))
select new Folder
{
Name = (string)query.Element("OriginalPath"),
};
ObservableCollection<Folder> LFolders = new ObservableCollection<Folder>();
foreach (Folder f in folders)
{
LFolders.Add(f);
}
listBox1.ItemsSource = LFolders;
listBox1.SelectionChanged += new SelectionChangedEventHandler(listBox1_SelectionChanged);
}
Two suggestions:
Consider using the MVVM pattern and then storing and updating your ObservableCollection on the view model instead.
Set the SelectionChanged event in XAML instead of where you're setting it now. For every call to this method you're appending an additional event handler to your listBox1.
If you set the Itemssource to null before you set the new value, I believe that will work. Also, you can try making LFolders a class variable. When you begin the method, clear the collection and then add to it. THe observable collection that is bound to the listbox will take care of updating the listbox.
I'm working on silverlight rss reader as schoolproject and I have one problem.
I want to define list of feed sources in xml and load this xml to list of button, each button for one feed.
xml looks like
<FeedList>
<Feed ButtonContent="HDRip's on RlsLog.net" Url="http://www.rlslog.net/category/movies/hdrip/feed/" />
</FeedList>
I'm loading this xml using linq and creating button
XDocument xdoc = XDocument.Load(string.Format("feeds.xml"));
if (xdoc != null)
{
var feedlist =
(from l in xdoc.Descendants("Feed")
select new MyButtons
{
Content = l.Attribute("ButtonContent").Value,
FeedUrl = l.Attribute("Url").Value
}
).ToList();
foreach (MyButtons feedbutton in feedlist)
{
Button b1 = new Button();
b1.Content = feedbutton.Content;
b1.Click += (s, e) => { feedViewer.LoadFeed(feedbutton.FeedUrl); };
ButtonPanel.Children.Add(b1);
}
}
button content is loading fine but that feed url is used the last one in xml for all buttons. could you please advice me what i'm doing wrong?
It looks like feedbutton is being captured as an iteration variable, thus causing the behavior you're describing when you assign the event handler with the lambda expression.
Try this instead:
MyButtons tempButton = feedbutton;
b1.Click += (s, e) => { feedViewer.LoadFeed(tempButton.FeedUrl); };
Eric Lippert has blogged about this topic:
Closing over the loop variable considered harmful
Closing over the loop variable, part two
Im not sure im going about this the right way or not, so hopefully somebody can help me.
Im trying to use a var in a method, which is contained in a different method. As expected, I get the error: The name 'Title1' does not exist in the current context .
first im reading an xml file then populating bing maps with pushpins. One of the variables is the tite of each xml item, I need to use the "Title1" var on my method below.
Here is the code:
public void OnOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
var document = XDocument.Load(e.Result);
if (document.Root == null)
return;
var xmlns = XNamespace.Get("http://www.blahblah.com");
var events = from ev in document.Descendants("item")
select new
{
Latitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "lat").Value),
Longitude = Convert.ToDouble(ev.Element(xmlns + "Point").Element(xmlns + "long").Value),
Title = (ev.Element("title").Value),
Description = (ev.Element("description").Value),
Link = (ev.Element("link").Value),
};
QuakeLayer.Children.Clear();
foreach (var ev in events)
{
var accentBrush = (Brush)Application.Current.Resources["PhoneAccentBrush"];
var Title1 = (ev.Title);
var pin = new Pushpin
{
Location = new GeoCoordinate
{
Latitude = ev.Latitude,
Longitude = ev.Longitude
},
Background = accentBrush,
Content = Title1
};
QuakeLayer.AddChild(pin, pin.Location);
}
}
public void Pushpin_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
NavigationService.Navigate(new Uri("/blahblah.xaml?info=" + Title1, UriKind.Relative));
}
If you use an anonymous method inside your loop you will be able to access this variable (magic happens during compilation):
var pin = new Pushpin
{
...
Content = Title1
};
pin.ManipulationStarted += (s, a) =>
{
// Code for the event here
// ... do something with Title1
};
QuakeLayer.AddChild(pin, pin.Location);
Is the PushPin the sender of the event? If so, you can get the title from it since you set that as it's content.
public void Pushpin_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
var pushPin = (PushPin)sender;
var title = pushPin.Content;
NavigationService.Navigate(new Uri("/blahblah.xaml?info=" + title, UriKind.Relative));
}
You can't reference local variables across different methods. For one thing, the local variable no longer exists when the function it's defined in returns. For another thing, it looks like the Title local variable will be assigned a lot of different values over the course of running through the events foreach loop, so moving the Title1 variable out to a class field won't solve anything.
Your best bet is probably to associate the Title1 with the pushpin object. What object is passed into your pushpin event as the sender? If that's the pushpin object itself, or the pushpin object is available via the event args parameter, then you're home free. The pushpin's Content property contains the Title1 value. Use Pushpin.Content instead of Title1.
Make it a class variable. Or pass it as an argument to another function. But the local variable is only valid inside a function. (A function can be called many times recursively. In that case, there are multiple copies of all the local variables in each stack frame. So what you're asking makes no sense.)
Since Title1 is created inside of a loop the only real "viable" method is for you to pass the item as a parameter to the object, OR to store he value that you need in a location that you could get it from. (Possibly as a Tag on the object that starts your other event).