Selecting the next radio button in a group - c#

Is there a standard way to programmatically select/check the next radio button in a group of radio buttons? The behaviour I'm looking for is similar to the default arrow key press event for radio buttons that are grouped in a container: When I press the arrow keys, the next (or previous) radio button is automatically selected and checked.

I made this in such way:
var rads = panel1.Controls.OfType<RadioButton>(); // Get all radioButtons of desired panel
rads.OrderBy(r => r.Top); // sort them. Please specify what you mean "next" here. I assume that you need next one at the bottom
// find first checked and set checked for next one
for (int i = 0; i < rads.Count()-1; i++)
{
if (rads.ElementAt(i).Checked)
{
rads.ElementAt(i + 1).Checked = true;
return;
}
}

You can only check one RadioButton per container. That's the point of a radio button. If you want to use a CheckBox instead you could use the following code:
foreach (CheckBox control in Controls.OfType<CheckBox>())
{
control.Checked = true;
}
If you want the controls checked sequentailly you can do
new Thread(() =>
{
foreach (CheckBox control in Controls.OfType<CheckBox>())
{
control.BeginInvoke((MethodInvoker) (() => control.Checked = true));
Thread.Sleep(500);
}
}).Start();
Reading your original post another time, I'm struggling to understand exactly what you mean. Could you please elaborate so I can update my response?

Quickest solution: (if your application has focus...)
//assuming you are at the first radio button
SendKeys({DOWN});
More Difficult Solution:
http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.findall.aspx
//Write method to get window element for the window you wish to manipulate
//Open an instance of notepad and the WindowTitle is: "Untitled - Notepad"
//you could use other means of getting to the Window element ...
AutomationElement windowElement = getWindowElement("Untitled - Notepad");
//Use System.Windows.Automation to find all radio buttons in the WindowElement
//pass the window element into this method
//This method will return all of the radio buttons in the element that is passed in
//however, if you have a Pane inside of the WIndow and then, the buttons are contained
//in the pane, you will have to get to the pane and then pass the pane into the findradiobuttons method
AutomationElementCollection radioButtons = FindRadioButtons(windowElement);
//could iterate through the radioButtons to determine which is selected...
//then select the next index etc.
//then programmatically select the radio button
//pass the selected radioButton AutomationElement into a method that Invokes the Click etc.
clickButtonUsingUIAutomation(radioButtons[0]);
/// <summary>
/// Finds all enabled buttons in the specified window element.
/// </summary>
/// <param name="elementWindowElement">An application or dialog window.</param>
/// <returns>A collection of elements that meet the conditions.</returns>
AutomationElementCollection FindRadioButtons(AutomationElement elementWindowElement)
{
if (elementWindowElement == null)
{
throw new ArgumentException();
}
Condition conditions = new AndCondition(
new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ControlTypeProperty,
ControlType.RadioButton)
);
// Find all children that match the specified conditions.
AutomationElementCollection elementCollection =
elementWindowElement.FindAll(TreeScope.Children, conditions);
return elementCollection;
}
private AutomationElement getWindowElement(string windowTitle)
{
AutomationElement root = AutomationElement.RootElement;
AutomationElement result = null;
foreach (AutomationElement window in root.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)))
{
try
{
if (window.Current.Name.Contains(windowTitle) && window.Current.IsKeyboardFocusable)
{
result = window;
break;
}
}
catch (Exception e)
{
throw;
}
}
return result;
}
private void ClickButtonUsingUIAutomation(AutomationElement control)
{
// Test for the control patterns of interest for this sample.
object objPattern;
ExpandCollapsePattern expcolPattern;
if (true == control.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out objPattern))
{
expcolPattern = objPattern as ExpandCollapsePattern;
if (expcolPattern.Current.ExpandCollapseState != ExpandCollapseState.LeafNode)
{
Button expcolButton = new Button();
//expcolButton.Margin = new Thickness(0, 0, 0, 5);
expcolButton.Height = 20;
expcolButton.Width = 100;
//expcolButton.Content = "ExpandCollapse";
expcolButton.Tag = expcolPattern;
expcolPattern.Expand();
//SelectListItem(control, "ProcessMethods");
//expcolButton.Click += new RoutedEventHandler(ExpandCollapse_Click);
//clientTreeViews[treeviewIndex].Children.Add(expcolButton);
}
}
TogglePattern togPattern;
if (true == control.TryGetCurrentPattern(TogglePattern.Pattern, out objPattern))
{
togPattern = objPattern as TogglePattern;
Button togButton = new Button();
//togButton.Margin = new Thickness(0, 0, 0, 5);
togButton.Height = 20;
togButton.Width = 100;
//togButton.Content = "Toggle";
togButton.Tag = togPattern;
togPattern.Toggle();
//togButton.Click += new RoutedEventHandler(Toggle_Click);
//clientTreeViews[treeviewIndex].Children.Add(togButton);
}
InvokePattern invPattern;
if (true == control.TryGetCurrentPattern(InvokePattern.Pattern, out objPattern))
{
invPattern = objPattern as InvokePattern;
Button invButton = new Button();
//invButton.Margin = new Thickness(0);
invButton.Height = 20;
invButton.Width = 100;
//invButton.Content = "Invoke";
invButton.Tag = invPattern;
//invButton.Click += new EventHandler(Invoke_Click);
invPattern.Invoke();
//clientTreeViews[treeviewIndex].Children.Add(invButton);
}
}

So, it seems as if there is no standard way to handle this. I ended up taking inspiration from Andrey's solution and writing an extension method that can be called from any particular RadioButton within a group of RadioButtons.
public static void CheckNextInGroup(this RadioButton radioButton, bool forward) {
var parent = radioButton.Parent;
var radioButtons = parent.Controls.OfType<RadioButton>(); //get all RadioButtons in the relevant container
var ordered = radioButtons.OrderBy(i => i.TabIndex).ThenBy(i => parent.Controls.GetChildIndex(i)).ToList(); //Sort them like Windows does
var indexChecked = ordered.IndexOf(radioButtons.Single(i => i.Checked)); //Find the index of the one currently checked
var indexDesired = (indexChecked + (forward ? 1 : -1)) % ordered.Count; //This allows you to step forward and loop back to the first RadioButton
if (indexDesired < 0) indexDesired += ordered.Count; //Allows you to step backwards to loop to the last RadioButton
ordered[indexDesired].Checked = true;
}
Then, from anywhere that has access to your particular RadioButton, you can cause the next or previous RadioButton in its collection to get checked. Like this:
radioButton1.CheckNextInGroup(true); //Checks the next one in the collection
radioButton1.CheckNextInGroup(false); //Checks the previous one in the collection

Related

C#, deleting all the controls with the same .Tag

My problem is that when I compare two, absolutely the same tags, they turn out to be different instead.
The description of a code: I create 3 controls (button, label and a textbox) and assign the same tag (let's say, 0) to them all. When I press this newly created button, I want to delete all of the 3 controls with the same tag.
Code for adding the buttons (simplified):
int Count = 0; // This var changes, but for the example it is 0
Button newButton = new Button();
newButton.Tag = Count;
newButton.Click += new EventHandler(DeleteName);
Controls.Add(newButton);
Label newLabel = new Label();
newLabel.Tag = Count;
Controls.Add(newLabel);
And the same for a TextBox.
The coding behind deleting:
private void DeleteName(object sender, EventArgs e)
{
List<Control> toDelete = new List<Control>();
Button btn = sender as Button;
foreach (Control c in Controls)
{
if (c.Tag == btn.Tag)
toDelete.Add(c);
}
int tmp = toDelete.Count;
for (int i = 0; i < tmp; i++)
{
Controls.Remove(toDelete[i]);
}
}
It used to work perfectly when I did the same logic before, but now it just deletes the button and no other control (the textbox and label stay untouched). When I replace "if (c.Tag == btn.Tag)" with, for example, "if (c is TextBox)", it adds all the TextBoxes to the list and deletes them, so I believe the problem is in this comparison of Tags.
The likely reason you're running into this is that Tag is an object, so the equality comparison will do a reference comparison, but you're setting it to a value type. And even if you were setting it to a reference type, the comparison would only return true if they were pointing to the exact same object.
One way to solve this would be to cast the Tag to the appropriate type, or, more commonly, use the ToString() method for comparison. We should also add null conditional operators in the check, in case a Tag is null, to avoid a NullReferenceException:
if (c.Tag?.ToString() == btn.Tag?.ToString())
Another thing you might consider doing is creating a separate method to delete all controls with a particular tag, and another one that recursively finds all controls with that tag. This will help out if you are in a situation where you have some controls with the "delete" tag that are children of a different container.
For example:
// Returns all controls under the parent with the specified tag
private List<Control> GetTaggedControls(string tag, Control parent)
{
var taggedControls = new List<Control>();
foreach (Control control in parent.Controls)
{
if (control.Tag?.ToString() == tag)
{
taggedControls.Add(control);
}
// Recursively call this method in case this is a container
taggedControls.AddRange(GetTaggedControls(tag, control));
}
return taggedControls;
}
// Deletes all controls with the specified tag
private void DeleteControlsWithTag(string tag)
{
foreach (Control control in GetTaggedControls(tag, this))
{
Controls.Remove(control);
}
}
For example, if you add the code below to a new WinForm project, it will create 10 labels with every other one containing a Tag with "deleteMe". It also creates a button that, when clicked, calls the DeleteControlsWithTag method above:
private void Form1_Load(object sender, EventArgs e)
{
var padSpace = 5;
var height = 15;
// Add 10 labels, with a common tag on every other one
for (int i = 0; i < 10; i++)
{
Controls.Add(new Label
{
Text = $"Label {i}",
Top = padSpace * (i + 1) + height * i,
Height = height,
Visible = true,
Tag = i % 2 == 0 ? "deleteMe" : "saveMe"
});
}
// Add a button that will delete controls with deleteMe tag
var btn = new Button
{
Text = "Delete Even Labels",
Height = 20,
Width = 150,
Top = padSpace * 11 + height * 10,
Visible = true
};
btn.Click += (o, ea) => { DeleteControlsWithTag("deleteMe"); };
Controls.Add(btn);
}

Edit ListView Group Headers like we can edit ListViewItems

In a WinForms app, we can re-name ListView Items by clicking them twice. Can we somehow rename Group Headers the same way? Is there a way to enable this?
I guess it is doable after all, albeit not by simply enabling a property..
Note: The code below assumes that the ListView is in Details mode!
The trick to tell a Group from emtpy space is to test the right side of the ListView. Another trick is to wait a little: The click will select the Group Items. Only after that can we proceed..
Here is an example that overlays the Group with a TextBox:
// class variable to test if have been hit twice in a row
ListViewGroup lastHitGroup = null;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
// check left side to see if we are at the empty space
ListViewItem lvi = listView1.GetItemAt(4, e.Y);
// yes, no action! reset group
if (lvi != null) { lastHitGroup = null; return; }
// get the height of an Item
int ih = listView1.GetItemRect(0).Height;
// to get the group we need to check the next item:
ListViewItem lviNext = listView1.GetItemAt(4, e.Y + ih);
// no next item, maybe the group is emtpy, no action
if (lviNext == null) return;
// this is our group
ListViewGroup editedGroup = lviNext.Group;
// is this the 2nd time?
if (lastHitGroup != editedGroup) {lastHitGroup = editedGroup; return;}
// we overlay a TextBox
TextBox tb = new TextBox();
tb.Parent = listView1;
// set width as you like!
tb.Height = ih;
// we position it over the group header and show it
tb.Location = new Point(0, lviNext.Position.Y - ih - 4);
tb.Show();
// we need two events to quit editing
tb.KeyPress += (ss, ee) =>
{
if (ee.KeyChar == (char)13) // success
{
if (editedGroup != null && tb.Text.Length > 0)
editedGroup.Header = tb.Text;
tb.Hide();
ee.Handled = true;
}
else if (ee.KeyChar == (char)27) // abort
{
tb.Text = ""; tb.Hide(); ee.Handled = true;
}
};
tb.LostFocus += (ss, ee) => // more success
{
if (editedGroup != null && tb.Text.Length > 0)
editedGroup.Header = tb.Text;
tb.Hide();
};
// we need to wait a little until the group items have been selected
Timer lvTimer = new Timer();
lvTimer.Interval = 333; // could take longer for a huge number of items!
lvTimer.Tick += (ss,ee) => { tb.Focus(); lvTimer.Stop();};
lvTimer.Start();
}

How to properly change the image of a button I just clicked?

Here is my dilemma. I have a set of 10 buttons that I use to rate something. They have a star image that when pressed gets changed from a grey one to a red one. If I press the star number 5 all the previous ones also get changed to red.
My problem is that the star that is clicked does not change its image so I found a workaround to introduce a pause inside a dispatcher block. I don't see this very elegant but it works and I was wondering if someone has a better approach.
Even if nobody finds a better way at least this code will help other people to do stuff with multiple buttons that do almost the same thing or browsing through controls in a panel.
Here is the code of the click event:
private void btnStar_Click(object sender, RoutedEventArgs e)
{
//First we get the number of the star from the control name
String strNum = (sender as Button).Name;
strNum = strNum.Replace("btnStar", "");
int idxSelected = int.Parse(strNum);
Debug.WriteLine("Selected star #" + strNum);
//We store the image ON an OFF to be used later
ImageBrush imgbrON = new ImageBrush();
imgbrON.ImageSource = new BitmapImage(new Uri("images/estrella_on.png", UriKind.Relative));
imgbrON.Stretch = Stretch.None;
ImageBrush imgbrOFF = new ImageBrush();
imgbrOFF.ImageSource = new BitmapImage(new Uri("images/estrella_off.png", UriKind.Relative));
imgbrOFF.Stretch = Stretch.None;
//If we pressed the first star when only the first was selected we reset all
if (idxSelected == 1 && iCurrentNumberOfStars == 1)
{
idxSelected = 0; //In order to deselect all stars
Debug.WriteLine("Deselect all");
}
else
{
//Here is the code to do the WORKAROUND to select the clicked star
Dispatcher.BeginInvoke(() =>
{
Thread.Sleep(500);
(sender as Button).Background = imgbrON;
});
}
iCurrentNumberOfStars = idxSelected;
foreach (UIElement child in ContentPanel.Children)
{
Thread.Sleep(10);
if (child.GetType().Name == "Button")
{
Button tmpButton = (child as Button);
Image content = tmpButton.Content as Image;
strNum = tmpButton.Name;
if (strNum.StartsWith("btnStar") == true)
{
strNum = strNum.Replace("btnStar", "");
int idxtmp = int.Parse(strNum);
if (idxtmp > idxSelected )
{
Debug.WriteLine(tmpButton.Name + ":OFF");
tmpButton.Background = imgbrOFF;
}
else
{
Debug.WriteLine(tmpButton.Name +":ON");
tmpButton.Background = imgbrON;
}
}
}
}
}
}
The reason why it's happens - Button has a visual states which manipulate the background property. When you try to set the background to your own - animation overrides your changes. If you remove background animation from visual states - it will work without a Delay.

Visual Studio-style undo drop-down button - custom ToolStripSplitButton

I'm looking to implement a Visual Studio-style undo drop-down button:
I've looked all over the internet, and can't seem to find any real implementations of this.
I've started by deriving from ToolStripSplitButton, but don't really know where to go from there. Its DropDown property is a ToolStripDropDown, but that doesn't seem to have anything regarding multiple items being selected, much less scrolling, and the text at the bottom.
So instead of the default ToolStripDropDown, I'm thinking maybe the whole drop down part should be a custom control, based on a combobox. The question then, is how to cause the right-side (drop down arrow) button to do something other than show its default drop down?
Am I on the right track here? Thanks!
Yes, I think you're on the right track. And in this case, ToolStripControlHost is your friend.
You don't necessarily need to derive from it (unless you are making your own control), but try just subscribing to the ToolStripSplitButton's DropDownOpening event:
Working example:
private ListBox listBox1;
public Form1()
{
InitializeComponent();
listBox1 = new ListBox();
listBox1.IntegralHeight = false;
listBox1.MinimumSize = new Size(120, 120); \\ <- important
listBox1.Items.Add("Item 1");
listBox1.Items.Add("Item 2");
}
private void toolStripSplitButton1_DropDownOpening(object sender, EventArgs e) {
ToolStripControlHost toolHost = new ToolStripControlHost(listBox1);
toolHost.Size = new Size(120, 120);
toolHost.Margin = new Padding(0);
ToolStripDropDown toolDrop = new ToolStripDropDown();
toolDrop.Padding = new Padding(0);
toolDrop.Items.Add(toolHost);
toolDrop.Show(this, new Point(toolStripSplitButton1.Bounds.Left,
toolStripSplitButton1.Bounds.Bottom));
}
Here is the result:
For your application, you would need to replace the ListBox with your own UserControl, so you can contain whatever your want in it. The ToolStripControlHost can only hold one control, and it's important to set the MinimumSize property, or else the dropped control isn't sized correctly.
Extra thanks to LarsTech! (I didn't know about ToolStripControlHost a few hours ago)
Here is my implementation, which is really close to the VS drop down...
You should be able to just drop this delegate & function into your Form:
public delegate void UndoRedoCallback(int count);
private void DrawDropDown(ToolStripSplitButton button, string action, IEnumerable<string> commands, UndoRedoCallback callback)
{
int width = 277;
int listHeight = 181;
int textHeight = 29;
Panel panel = new Panel()
{
Size = new Size(width, textHeight + listHeight),
Padding = new Padding(0),
Margin = new Padding(0),
BorderStyle = BorderStyle.FixedSingle,
};
Label label = new Label()
{
Size = new Size(width, textHeight),
Location = new Point(1, listHeight - 2),
TextAlign = ContentAlignment.MiddleCenter,
Text = String.Format("{0} 1 Action", action),
Padding = new Padding(0),
Margin = new Padding(0),
};
ListBox list = new ListBox()
{
Size = new Size(width, listHeight),
Location = new Point(1,1),
SelectionMode = SelectionMode.MultiSimple,
ScrollAlwaysVisible = true,
Padding = new Padding(0),
Margin = new Padding(0),
BorderStyle = BorderStyle.None,
Font = new Font(panel.Font.FontFamily, 9),
};
foreach (var item in commands) { list.Items.Add(item); }
if (list.Items.Count == 0) return;
list.SelectedIndex = 0;
ToolStripControlHost toolHost = new ToolStripControlHost(panel)
{
Size = panel.Size,
Margin = new Padding(0),
};
ToolStripDropDown toolDrop = new ToolStripDropDown()
{
Padding = new Padding(0),
};
toolDrop.Items.Add(toolHost);
panel.Controls.Add(list);
panel.Controls.Add(label);
toolDrop.Show(this, new Point(button.Bounds.Left + button.Owner.Left, button.Bounds.Bottom + button.Owner.Top));
// *Note: These will be "up values" that will exist beyond the scope of this function
int index = 1;
int lastIndex = 1;
list.Click += (sender, e) => { toolDrop.Close(); callback(index); };
list.MouseMove += (sender, e) =>
{
index = Math.Max(1, list.IndexFromPoint(e.Location) + 1);
if (lastIndex != index)
{
int topIndex = Math.Max(0, Math.Min(list.TopIndex + e.Delta, list.Items.Count - 1));
list.BeginUpdate();
list.ClearSelected();
for (int i = 0; i < index; ++i) { list.SelectedIndex = i; }
label.Text = String.Format("{0} {1} Action{2}", action, index, index == 1 ? "" : "s");
lastIndex = index;
list.EndUpdate();
list.TopIndex = topIndex;
}
};
list.Focus();
}
You can set it up and test like this, assuming you have a blank form (Form1) with a toolStrip that has 1 ToolStripSplitButton (toolStripSplitButton1) added:
public Form1()
{
InitializeComponent();
// Call DrawDropDown with:
// The clicked ToolStripSplitButton
// "Undo" as the action
// TestDropDown for the enumerable string source for the list box
// UndoCommands for the click callback
toolStripSplitButton1.DropDownOpening += (sender, e) => { DrawDropDown(
toolStripSplitButton1,
"Undo",
TestDropDown,
UndoCommands
); };
}
private IEnumerable<string> TestDropDown
{
// Provides a list of strings for testing the drop down
get { for (int i = 1; i < 1000; ++i) { yield return "test " + i; } }
}
private void UndoCommands(int count)
{
// Do something with the count when an action is clicked
Console.WriteLine("Undo: {0}", count);
}
Here is a better example using the Undo/Redo system from: http://www.codeproject.com/KB/cs/AutomatingUndoRedo.aspx
public Form1()
{
InitializeComponent();
// Call DrawDropDown with:
// The Undo ToolStripSplitButton button on the Standard tool strip
// "Undo" as the action name
// The list of UndoCommands from the UndoRedoManager
// The Undo method of the UndoRedoManager
m_TSSB_Standard_Undo.DropDownOpening += (sender, e) => { DrawDropDown(
m_TSSB_Standard_Undo,
"Undo",
UndoRedoManager.UndoCommands,
UndoRedoManager.Undo
); };
}
*Note: I did modify the Undo & Redo methods in the UndoRedoManager to accept a count:
// Based on code by Siarhei Arkhipenka (Sergey Arhipenko) (http://www.codeproject.com/KB/cs/AutomatingUndoRedo.aspx)
public static void Undo(int count)
{
AssertNoCommand();
if (CanUndo == false) return;
for (int i = 0; (i < count) && CanUndo; ++i)
{
Command command = history[currentPosition--];
foreach (IUndoRedo member in command.Keys)
{
member.OnUndo(command[member]);
}
}
OnCommandDone(CommandDoneType.Undo);
}
I'd suggest implementing the popup separately from the toolbar button. Popups are separate windows with a topmost-flag which auto-close when losing focus or pressing escape. If you code your own popup window that frees you from having to fit your behaviour to a preexisting model (which is going to be hard in your case). Just make a new topmost window with a listbox and status bar, then you are free to implement the selection behavior on the listbox like you need it.
Vs 2010 is a WPF application. If you are in the beginning of this application development than use WPF as a core technology. WPF drop down button is implemented in WPF ribbon. Source code is available on CodePlex.

How to change properties of a Control that is in List<UIControl> without using Loop?

I have the following code where a click event will dynamically create additional Canvas to the WrapPanel, and each Canvas contains a TextBox and a Button. Once the Button on one Canvas is click, TextBox.Text and Button.Content change from "Foo" to "Jesus".
The below code works, but it's not ideal. Because each property Change ("Foo" to "Jesus), I have to run a loop. I have to run two loops just to change the text on the TextBox and Button. Is there a direct way to change the Properties other then a Loop? My actually application contains 30+ controls in a Canvas, I don't want to run 30+ loops each time just to change some text.
List<Canvas> cvList = new List<Canvas>();
List<TextBox> tbList = new List<TextBox>();
List<Button> FooList = new List<Button>();
WrapPanel wp = new WrapPanel();
private void createbtn1_Click(object sender, RoutedEventArgs e)
{
Canvas cv = new Canvas();
StackPanel sp = new StackPanel();
TextBox tb = new TextBox();
Button Foo = new Button();
sp.Orientation = Orientation.Vertical;
sp.Children.Add(tb);
sp.Children.Add(Foo);
cv.Children.Add(sp);
wp.Children.Add(cv);
cvList.Add(cv);
tbList.Add(tb);
FooList.Add(Foo);
cv.Width = 100;
cv.Height = 100;
tb.Text = "#" + (cvList.IndexOf(cv)+1);
tb.Width = 50;
tb.Height = 30;
Foo.Content = "Foo";
Foo.Click += destroy_Click;
}
private void Foo_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
var bIndex = FooList.IndexOf(b);
foreach (TextBox t in tbList)
{
if (tbList.IndexOf(t) == bIndex)
{
t.Text = "Jesus";
}
}
foreach (Button f in FooList)
{
if (FooList.IndexOf(t) == bIndex)
{
t.Content = "Jesus";
}
}
}
Just access the text boxes by index and set the content of the button directly:
if(bIndex < tbList.Count && bIndex != -1)
tbList[bIndex].Text = "Jesus";
if(b != null && bIndex != -1)
b.Content = "Jesus";
why can't you just get the item at the index and set that items text:
tbList[bindex].Text="Jesus";
As for setting the buttons content, you already have the button from the click event, so just use that:
b.Content = "Jesus";
You current code just loops through each item in the list and gets the index of the item and sees if it is the index you want. Accessing by the indexer of the list directly will give you what you want.
You will probably want to do some error checking, but that is not currently done in your existing code either.
Some info on using indexers from MSDN

Categories