Dynamic list of button with events from xml - c#

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

Related

Cant find control on a form there for need to trap for that

I am working with sage 200 summer edition and I have an issue with a piece of code I need to be able to do a null check on the Sage.Controls.Button but even if I place it into a var it will still crash its what is called an amendable button in windows forms.
While true most people will stright away suggest this is a null issue and how to solve it its not that quite simple casue I am dealing with sage objects I have searched for existing refersnces for the underlyingcontrol function and cannot find any documenation on the object or how to handle the null
If I just do the following
var control = (Sage.Common.Controls.Button)Form.FindControlByName("saveButton").UnderlyingControl;
And do an if statement around that it will still crash. Any Ideas how to handle this.
public override void ConfigureForm()
{
var sopOrderReturnLine = (Sage.Accounting.SOP.SOPOrderReturnLine) Form.BoundObjects[0];
_itemDescription = sopOrderReturnLine.ItemDescription;
if (sopOrderReturnLine.SOPOrderReturn.SpareBit1)
{
var button = new Button();
button.Name = "extraInfoButton";
button.Text = "Extra Info...";
button.Alignment = ButtonAlignmentInContainer.Left;
var buttonPanel = (ControlPanel) Form.FindControlByName("buttonPanel").UnderlyingControl;
buttonPanel.Controls.Add(button);
button.Click += new EventHandler(button_Click);
}
var control = (Sage.Common.Controls.Button)Form.FindControlByName("saveButton").UnderlyingControl;
if (control !=null)
{
var saveButton = (Sage.Common.Controls.Button)Form.FindControlByName("saveButton").UnderlyingControl;
saveButton.BeforeClick += new System.ComponentModel.CancelEventHandler(saveButton_BeforeClick);
}
}

C# Dynamically added button click is throwing argument out of bounds exception

Basically I am trying to create an attachment window utilizing keeping everything in lists for easy use later. So, every time the form loads it goes through everything in the list and creates both labels and buttons for them. There is no errors until I click my button. If I click any of the X buttons, I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself. Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated.
ArrayList attachmentFiles;
ArrayList attachmentNames;
public Attachments(ArrayList attachments, ArrayList attachmentFileNames)
{
InitializeComponent();
attachmentFiles = attachments;
attachmentNames = attachmentFileNames;
}
private void Attachments_Load(object sender, EventArgs e)
{
ScrollBar vScrollBar1 = new VScrollBar();
ScrollBar hScrollBar1 = new HScrollBar();
vScrollBar1.Dock = DockStyle.Right;
hScrollBar1.Dock = DockStyle.Bottom;
vScrollBar1.Scroll += (sender2, e2) => { pnl_Attachments.VerticalScroll.Value = vScrollBar1.Value; };
hScrollBar1.Scroll += (sender3, e4) => { pnl_Attachments.HorizontalScroll.Value = hScrollBar1.Value; };
pnl_Attachments.Controls.Add(hScrollBar1);
pnl_Attachments.Controls.Add(vScrollBar1);
Label fileName;
for (int i = 0; i < attachmentNames.Count; i++)
{
fileName = new Label();
fileName.AutoSize = true;
fileName.Text = attachmentNames[i].ToString();
fileName.Top = (i + 1) * 22;
pnl_Attachments.Controls.Add(fileName);
Button btn_RemoveAttachment = new Button();
btn_RemoveAttachment.Text = "X";
btn_RemoveAttachment.Tag = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => removeAttachment(s, e3, attachmentFiles[i].ToString(), attachmentNames[i].ToString()));
btn_RemoveAttachment.Top = (i + 1) * 22;
btn_RemoveAttachment.Left = fileName.Right + 22;
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
}
private void removeAttachment(object sender, EventArgs e, string file, string fileName)
{
attachmentNames.Remove(fileName);
attachmentFiles.Remove(file);
pnl_Attachments.Controls.Clear();
this.Close();
}
In my test, attachmentFiles had a count of 3 and attachmentNames had a count of 3. On form load, there are no issues. But, on button click I get an exception because somehow its trying to add another click listener to a button with i = 3 (a.k.a a 4th element)
The problem is not with the event subscription, but with the event handler execution.
You are running into this problem because a closure is created for the event handler, but the value i is modified by the for loop. The last value of i will be 1 + attachmentNames.Count and this will be the value used by each invocation of the event handler.
For more detail as to why this happens you can check out the question and answer here: Access to Modified Closure.
To resolve the problem, you can assign i to another variable:
var currentAttachmentIndex = i;
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Or you can use the value you already captured in the Tag property of the btn_RemoveAttachment control.
btn_RemoveAttachment.Click += new System.EventHandler((s, e3) => {
var senderButton = (Button)s;
var currentAttachmentIndex = (int)senderButton.Tag;
removeAttachment(s,
e3,
attachmentFiles[currentAttachmentIndex].ToString(),
attachmentNames[currentAttachmentIndex].ToString())
});
Keep in mind, though, if you are removing items from the List, the indexes will not be valid. Understanding how closures work, however, should help you solve that problem if it arises (it looks like you close the form anyway after the first removal).
Presumably, the attachmentFiles[i] is what is causing the out of bounds exception, perhaps attachmentFiles has fewer elements than attachmentNames?
Why not set a breakpoint on that line and check what is causing the out of bounds exception?
I get an argument out of bounds exception on the click += line. What's interesting is why its being called? The click isn't supposed to add another event handler to itself
It looks like the exception is not thrown at the event subscription (+=) but at the lambda function declared in that same line
Its also interesting that on click the indicie is one greater than the total count so how its even executing that line is beside me considering it should never iterate higher that its max count. Any help would be greatly appreciated
The value of i is fixed when you assign the lambda to the event. The indexes at the attachmentFiles change as you remove elements, but the indexes used by the lambda to access it don't. Let's try an example.
let's assume we have an array with 4 attchements (index:attachment))
[0:att0, 1:att1, 2:att2, 3:att3]
And 4 buttons that execute this lambdas
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
We click the second button and it correctly removes the second attachment from array, now we have:
[0:att0, 1:att2, 2:att3]
[removeAt(0), removeAt(1), removeAt(2), removeAt(3)]
Now we click the fourth button. It tries to remove the attachment with index 3 and the out of bounds exception is thrown because that index doesn't exist anymore (and even if it existed, it might not point to the right attachment!)
Another approach would be to modify your 'removeAttachment' method, and use that as your event handler for all buttons.
An example of this would be:
for (int i = 0; i < attachmentNames.Count; i++)
{
var lbl_FileName = new Label
{
AutoSize = true,
Name = "Label" + i, // Give this a name so we can find it later
Text = attachmentNames[i],
Top = (i + 1) * 22
};
var btn_RemoveAttachment = new Button
{
Text = "X",
Tag = i,
Top = (i + 1) * 22,
Left = lbl_FileName.Right + 22
};
btn_RemoveAttachment.Click += removeAttachment;
pnl_Attachments.Controls.Add(lbl_FileName);
pnl_Attachments.Controls.Add(btn_RemoveAttachment);
}
Then you can modify your removeAttachment method to be an EventHandler, and to detect the button and associated label using the sender As Button and Button.Tag property:
private void removeAttachment(object sender, EventArgs e)
{
// Get associated Label and Button controls
var thisButton = sender as Button;
var index = Convert.ToInt32(thisButton.Tag);
var thisLabel = (Label) Controls.Find("NameLabel" + index, true).First();
// Remove the files
int itemIndex = attachmentNames.IndexOf(thisLabel.Text);
attachmentNames.RemoveAt(itemIndex);
attachmentFiles.RemoveAt(itemIndex);
// Disable controls and strikethrough the text
thisButton.Enabled = false;
thisLabel.Font = new Font(thisLabel.Font, FontStyle.Strikeout);
thisLabel.Enabled = false;
}

How do I get data from multiple XML files in Windows Phone

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);
....

Pass a parameter to the ApplicationBarIconButton.Click

*Hi everyone,
I'm new in WP7 dev. (i'm used to work on android) and there is a basic thing i don't know how to do.
I create programmatically a list of ApplicationBarIconButton with this:
for (int i=0; i<menus.Count(); i++)
{
ApplicationBarIconButton button = new ApplicationBarIconButton();
button.IconUri = new Uri(menus.ElementAt(i).Element("ImageUrl").Value.Trim(),UriKind.Relative);
button.Text = menus.ElementAt(i).Element("Title").Value.Trim();
button.Click += new EventHandler(button_clicked);
ApplicationBar.Buttons.Add(button);
}
and I want that the button_clicked method could retrieve the i value of the button.
How is it possible?
Thanks
I was beaten to it by #Enigmativity but his answer may still be incorrect. In my experience I've found that you need to clone the iterating i variable, otherwise on the click event, i will be the last value. If his doesn't work try this (again using a lamba function)
for (int i=0; i<menus.Count(); i++){
ApplicationBarIconButton button = new ...
...
var cloned = i;
button.Click += (sender, e) => {
sometTextBlock.Text = String.Format("App Button {0} pressed.", cloned);
};
}
Cheers,
Al.
You could do this:
for (int i=0; i<menus.Count(); i++)
{
ApplicationBarIconButton button = new ApplicationBarIconButton();
button.IconUri = new Uri(menus.ElementAt(i).Element("ImageUrl").Value.Trim(),UriKind.Relative);
button.Text = menus.ElementAt(i).Element("Title").Value.Trim();
var i2 = i; //Thanks to `ajmccall` - I forgot this.
button.Click += (s, e) =>
{
// the variable `i2` is accessible now.
};
ApplicationBar.Buttons.Add(button);
}
Rather than calling a method to handle click you can use a lambda and still get access to i (via local copy i2). You could then call any method passing i2 as a parameter if you need to.
An integrated way of achieving this is through the use of commanding in MVVM frameworks.
Granted with Application bar buttons / menu items it is a bit more tricky but far more flexible than to manipulate the UI Elements on the page.
Look in to MVVM light (http://mvvmlight.codeplex.com) or further with the likes of Calburn.Micro (http://caliburnmicro.codeplex.com/)
For application bar data binding you will need to google further (lost the link at the mo)

Iterating over a List and using the objects inside an anonymous function

I have a List<string> and I want to iterate over this collection and do something with each string on a button click. I have a small example here to illustrate what I'm trying to do:
//items is a System.Collections.Generic.List<string>
foreach (string s in items)
{
Button b = new Button() { Content = s };
b.Click += (obj, ev) =>
{
MessageBox.Show(s);
}
//add b to form, container, etc...
}
As you would expect the buttons are created appropriately with the correct content, however when I click any of the buttons, the text inside the MessageBox is always the last string in items. What am I missing with this? Why is it that all the Click functions for the buttons are passed the last item in the collection?
The foreach loop is changing s, which is used in the lambda. The lambda uses the current value of s at the point of execution, not declaring it (in techspeak: "closures close over variables, not values"). You'll have to make a local variable:
foreach (string s in items)
{
string local = s;
Button b = new Button() { Content = s };
b.Click += (obj, ev) =>
{
MessageBox.Show(local);
}
//add b to form, container, etc...
}
Thus you have a reference to the instance of s at the point of decleration, not execution.
Eric Lippert has two fantastic articles about it: part 1, part 2.

Categories