WinForms TreeView with both radios and checkboxes - winforms

I have a case where I would like TreeView to be able to show radio buttons on multiple root nodes, and checkboxes on their children. There would only be one level of children beneath any root node.
The radios should also behave like a group, ie one root is selected and the others' radios deselect.
I've been trying to fake it with images, but it doesn't look realistic. I originally had a listbox and a separate checkedlistbox, but the usability gods struck it down.
Has anyone implemented this functionality or have another suggestion?
Think of it this way:
(o) McDonalds
[ ] Burger
[ ] Fries
[ ] Drink
(o) Burger King
[ ] Burger
[ ] Fries
[ ] Drink
(*) Wendy's
[x] Burger
[x] Fries
[ ] Drink
You can have one big option, but select 1..n underneath the big option.

I came up with a solution based on the article http://www.codeproject.com/KB/combobox/RadioListBoxDotNetVersion.aspx. My implementation inherits from CheckedListBox and includes the following methods:
protected override void OnDrawItem (DrawItemEventArgs e)
{
// Erase all background if control has no items
if (e.Index < 0 || e.Index > this.Items.Count - 1)
{
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
return;
}
// Calculate bounds for background, if last item paint up to bottom of control
Rectangle rectBack = e.Bounds;
if (e.Index == this.Items.Count - 1)
rectBack.Height = this.ClientRectangle.Top + this.ClientRectangle.Height - e.Bounds.Top;
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), rectBack);
// Determines text color/brush
Brush brushText = SystemBrushes.FromSystemColor(this.ForeColor);
if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
brushText = SystemBrushes.GrayText;
Boolean bIsChecked = this.GetItemChecked(e.Index);
String strText;
if (!string.IsNullOrEmpty(DisplayMember)) // Bound Datatable? Then show the column written in Displaymember
strText = ((System.Data.DataRowView) this.Items[e.Index])[this.DisplayMember].ToString();
else
strText = this.Items[e.Index].ToString();
Size sizeGlyph;
Point ptGlyph;
if (this.GetItemType(e.Index) == ItemType.Radio)
{
RadioButtonState stateRadio = bIsChecked ? RadioButtonState.CheckedNormal : RadioButtonState.UncheckedNormal;
if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
stateRadio = bIsChecked ? RadioButtonState.CheckedDisabled : RadioButtonState.UncheckedDisabled;
// Determines bounds for text and radio button
sizeGlyph = RadioButtonRenderer.GetGlyphSize(e.Graphics, stateRadio);
ptGlyph = e.Bounds.Location;
ptGlyph.X += 4; // a comfortable distance from the edge
ptGlyph.Y += (e.Bounds.Height - sizeGlyph.Height) / 2;
// Draws the radio button
RadioButtonRenderer.DrawRadioButton(e.Graphics, ptGlyph, stateRadio);
}
else
{
CheckBoxState stateCheck = bIsChecked ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;
if ((e.State & DrawItemState.Disabled) == DrawItemState.Disabled || (e.State & DrawItemState.Grayed) == DrawItemState.Grayed)
stateCheck = bIsChecked ? CheckBoxState.CheckedDisabled : CheckBoxState.UncheckedDisabled;
// Determines bounds for text and radio button
sizeGlyph = CheckBoxRenderer.GetGlyphSize(e.Graphics, stateCheck);
ptGlyph = e.Bounds.Location;
ptGlyph.X += 20; // a comfortable distance from the edge
ptGlyph.Y += (e.Bounds.Height - sizeGlyph.Height) / 2;
// Draws the radio button
CheckBoxRenderer.DrawCheckBox(e.Graphics, ptGlyph, stateCheck);
}
// Draws the text
Rectangle rectText = new Rectangle(ptGlyph.X + sizeGlyph.Width + 3, e.Bounds.Y, e.Bounds.Width - sizeGlyph.Width, e.Bounds.Height);
e.Graphics.DrawString(strText.Substring(4), e.Font, brushText, rectText, this.oAlign);
// If the ListBox has focus, draw a focus rectangle around the selected item.
e.DrawFocusRectangle();
}
protected override void OnItemCheck (ItemCheckEventArgs ice)
{
base.OnItemCheck(ice);
if (ice.NewValue == CheckState.Unchecked)
return;
if (this.GetItemType(ice.Index) == ItemType.Radio) // if they selected a root, deselect other roots and their children
{
for (Int32 i = 0; i < this.Items.Count; ++i)
{
if (i == ice.Index)
continue;
if (this.GetItemType(i) == ItemType.Radio)
{
this.SetItemChecked(i, false);
Int32 j = i + 1;
while (j < this.Items.Count && this.GetItemType(j) == ItemType.Checkbox)
{
this.SetItemChecked(j, false);
j++;
}
}
}
}
else if (this.GetItemType(ice.Index) == ItemType.Checkbox) // they selected a child; select the root too and deselect other roots and their children
{
// Find parent
Int32 iParentIdx = ice.Index - 1;
while (iParentIdx >= 0 && this.GetItemType(iParentIdx) == ItemType.Checkbox)
iParentIdx--;
this.SetItemChecked(iParentIdx, true);
}
}
protected ItemType GetItemType (Int32 iIdx)
{
String strText = this.Items[iIdx].ToString();
if (strText.StartsWith("(o)"))
return (ItemType.Radio);
else if (strText.StartsWith("[x]"))
return (ItemType.Checkbox);
throw (new Exception("Invalid item type"));
}

Related

Efficiently navigating arrays and selected strings

I am a noob playing around with actionscript but i feel this question is a basic coding questionMy project is similar to this picture.
I have four quadrant areas (Red, blue, yellow, and green) that I am adding text buttons to each area with a single word in each button. There are 16 words in each section that are added from 4 arrays that have the preset words (redWordArray, greenWordArray, yellowWordArray, blueWordArray). When clicked, the text button glows using a glow filter and the word gets added to another array for data collecting. For instance, a red word when clicked gets added to a red array (redChosenArray). When the word is clicked again, it removes the glow filter and is removed from the chosen array.
I am finding that my performance is slow and I am wondering if I am adding and deleting words correctly and efficiently. These are my functions for adding the glow filters and the selected word to the array. I would love your insights for best coding practices as I am sure it is a mess!
Thank you!
function selectWord(event:MouseEvent):void
{
var tempWord:String = event.currentTarget.mood.text;
var tempArray:Array;
if (event.currentTarget.clicked == false)
{
event.currentTarget.filters = filterArray;
event.currentTarget.clicked = true;
tempArray = addToArray(tempWord)
tempArray.push(tempWord);
trace(redChosen);
trace(blueChosen);
trace(yellowChosen);
trace(greenChosen);
trace("");
}else if(event.currentTarget.clicked == true)
{
event.currentTarget.filters = emptyFilterArray;
event.currentTarget.clicked = false;
removeMoodWord(tempWord);
trace(redChosen);
trace(blueChosen);
trace(yellowChosen);
trace(greenChosen);
trace("");
}
}
function addToArray(moodWord:String):Array
{
var wordFound:Boolean = false;
var allWords:int = 16;
var chosenArray:Array;
while (!wordFound)
{
for (var h:int = 0; h < allWords; h++)
{
if (moodWord == redWords[h])
{
chosenArray = redChosen;
wordFound = true;
}else if (moodWord == yellowWords[h])
{
chosenArray = yellowChosen
wordFound = true;
}else if (moodWord == greenWords[h])
{
chosenArray = greenChosen
wordFound = true;
}else if (moodWord == blueWords[h])
{
chosenArray = blueChosen
wordFound = true;
}
}
}
return chosenArray;
}
function removeMoodWord(moodWord:String):void
{
if (redChosen.indexOf(moodWord) >= 0)
{
redChosen.splice(redChosen.indexOf(moodWord), 1);
}else if (blueChosen.indexOf(moodWord) >= 0)
{
blueChosen.splice(blueChosen.indexOf(moodWord), 1);
}else if (yellowChosen.indexOf(moodWord) >= 0)
{
yellowChosen.splice(yellowChosen.indexOf(moodWord), 1);
}else if (greenChosen.indexOf(moodWord) >= 0)
{
greenChosen.splice(greenChosen.indexOf(moodWord), 1);
}
i fee}

textrange losing textpointer reference?

I've been using some code I found here, to try and change the case of text in a flow document. It changes the text properly however all the formatting is lost (bold, italics, etc) and when I save the document to a XML file all the text ends up in the first Run of the document and all the other Run's are empty.
private void ChangeCase()
{
TextPointer start = mergedDocument.ContentStart;
TextPointer end = mergedDocument.ContentEnd;
List<TextRange> textToChange = SplitToTextRanges(start, end);
ChangeCaseToAllRanges(textToChange);
}
private List<TextRange> SplitToTextRanges(TextPointer start, TextPointer end)
{
List<TextRange> textToChange = new List<TextRange>();
var previousPointer = start;
for (var pointer = start; (pointer != null && pointer.CompareTo(end) <= 0); pointer = pointer.GetPositionAtOffset(1, LogicalDirection.Forward))
{
var contextAfter = pointer.GetPointerContext(LogicalDirection.Forward);
var contextBefore = pointer.GetPointerContext(LogicalDirection.Backward);
if (contextBefore != TextPointerContext.Text && contextAfter == TextPointerContext.Text)
{
previousPointer = pointer;
}
if (contextBefore == TextPointerContext.Text && contextAfter != TextPointerContext.Text && previousPointer != pointer)
{
textToChange.Add(new TextRange(previousPointer, pointer));
previousPointer = null;
}
}
textToChange.Add(new TextRange(previousPointer ?? end, end));
return textToChange;
}
private void ChangeCaseToAllRanges(List<TextRange> textToChange)
{
Func<string, string> caseChanger;
ComboBoxItem cbi = cb_Case.SelectedItem as ComboBoxItem;
var textInfo = CultureInfo.CurrentUICulture.TextInfo;
if (cbi == null || (string)cbi.Tag == "none")
{
return;
}
else if((string)cbi.Tag == "title")
{
caseChanger = (text) => textInfo.ToTitleCase(text);
}
else if ((string)cbi.Tag == "upper")
{
caseChanger = (text) => textInfo.ToUpper(text);
}
else if ((string)cbi.Tag == "lower")
{
caseChanger = (text) => textInfo.ToLower(text);
}
else
return;
foreach (var range in textToChange)
{
if (!range.IsEmpty && !string.IsNullOrWhiteSpace(range.Text))
{
System.Diagnostics.Debug.WriteLine("Casing: " + range.Text);
System.Diagnostics.Debug.WriteLine("\tat: " +
range.Start.GetOffsetToPosition(mergedDocument.ContentStart) +
" ," +
range.End.GetOffsetToPosition(mergedDocument.ContentStart));
range.Text = caseChanger(range.Text);
}
}
}
I can't see any reason why this code isn't working properly. It looks like the textpointers in the textrange object are being redirected to the beginning of the document.
When setting TextRange.Text, it first deletes the selection by telling the TextContainer (the FlowDocument) to delete that content. If that content happened to be an entire Inline with your styling dependency properties then goodbye! So, not only does it get unstyled text, but it sets it
Since you want to keep your existing inline objects you can iterate through the entire FlowDocument to find them and set their text property.
Here is a helper method that only supports Paragraphs and finds all inlines in the entire selection (this logic is a lot simpler if you are always doing Document.ContentStart and Document.ContentEnd). You can expand this to include the inlines inside of Lists, ListItems, and Hyperlinks if you need to (by following a similar pattern).
You should then be able to set the Text property on each of these inlines.
List<Inline> GetInlines(TextRange selection)
{
var inlines = new List<Inline>();
foreach (var block in Document.Blocks.Where(x => selection.Start.CompareTo(x.ContentEnd) < 0 && selection.End.CompareTo(x.ContentStart) > 0))
{
var paragraph = block as Paragraph;
if (paragraph != null)
{
inlines.AddRange(paragraph.Inlines.Where(x => selection.Start.CompareTo(x.ContentEnd) < 0 && selection.End.CompareTo(x.ContentStart) > 0));
}
}
return inlines;
Edit: You will want to cast these to Run or Span to access the text property. You can even just remove Inline and get these types (probably just Run).

MouseEnter Stack

In the code that follows,was trying get the border that was clicked the name or the text of the child that's a textblock my problem is that without "eaclick.Handled = true;" in the code it starts showing me all the Names of the border that the mouse has entered before the click and not not only the oned that was clicked by adding "eaclick.Handled = true;" shows me alls the first border that the mouse has entered,it seems to me that's saving in a stack all the mouseenters and when Click in leftmousedown it goes get that stack instand of getting me the last mouseenter that I want can anyone explain me how to fix or what I am doing wrong?
for (int i = 0; i < NumPages; i++)
{
Border borderaux = new Border();
borderaux.Name = Convert.ToString(i);
//borderaux.MouseEnter += borderaux_MouseEnter;
Border clicked;
borderaux.MouseEnter += (smouse, eamouse) =>
{
clicked = (Border)smouse;
clicked.Cursor = Cursors.Hand;
MouseLeftButtonDown += (sclick, eaclick) =>
{
if (eaclick.ClickCount == 1)
{
TextBlock opcao = (TextBlock)(clicked).Child;
//string opcao="";
MessageBox.Show("Pressed-->" + opcao.Text);
//MessageBox.Show("Pressed-->" + clicked.Name);
eaclick.Handled = true;
}
};
the problem was that using the MouseLeftButtonDown event inside MouseEnter event that was giving the problem,that when was clicked it would show me x MessageBox showing all the number of borders that the mouse had hovered until the click,with the fix it show me the border that was actualy clicked.
for (int i = 0; i < NumPages; i++)
{
Border borderaux = new Border();
borderaux.Name = Convert.ToString(i);
//borderaux.MouseEnter += borderaux_MouseEnter;
Border clicked;
borderaux.MouseLeftButtonDown += (sclick, eaclick) =>
{
if (eaclick.ClickCount == 1)
{
TextBlock opcao = (TextBlock)((Border)sclick).Child;
//string opcao="";
MessageBox.Show("Pressed-->" + opcao.Text);
//eaclick.Handled = true;
}
};
borderaux.MouseEnter += (smouse, eamouse) =>
{
clicked = (Border)smouse;
clicked.Cursor = Cursors.Hand;
};

Getting new line in wpf

i am having arraylist.let say it contains 15 elements. I am adding these to stack panel. I need to add 3 elements per line. my code is below. I am getting either horizontal or verrtical.Let me know how to do this.
MainWindow w;
public ShopCart(MainWindow m,ArrayList _list)
{
InitializeComponent();
w = m;
int i = 1;
foreach (string cartitems in _list)
{
mystackpanel.Orientation = Orientation.Horizontal;
mystackpanel.Margin.Left.Equals(150);
Label lbl = new Label();
lbl.Name = "Label" + i;
lbl.Height = 30;
lbl.Width = 200;
lbl.Margin.Left.Equals(150);
//lbl.Margin.Top.Equals(150);
lbl.Content = cartitems.ToString();
mystackpanel.Children.Add(lbl);
i++;
int str = mystackpanel.Children.Count;
MessageBox.Show(Convert.ToString(str));
if (str%3 == 0)
{
Button btndelete = new Button();
btndelete.Content = "Delete";
btndelete.Width = 120;
btndelete.Height = 35;
mystackpanel.Children.Add(btndelete);
mystackpanel.Margin.Top.Equals(500);
}
}
Here is some sample code(assuming you already have a list of buttons, and will add outer stack panel to your main control) you can try, you may need to change few things as per your need:
List<Button> buttons = new List<Button>();
StackPanel panel = new StackPanel();
panel.Orientation = Orientation.Horizontal;
int count = 0;
StackPanel innerPanel = new StackPanel();
innerPanel.Orientation = Orientation.Vertical;
foreach (Button button in buttons)
{
innerPanel.Children.Add(button);
++count;
if (count % 3 == 0 && count != 0)
{
panel.Children.Add(innerPanel);
innerPanel = new StackPanel();
innerPanel.Orientation = Orientation.Vertical;
}
}
if (panel.Children.Contains(innerPanel) == false)
{
panel.Children.Add(innerPanel);
}
Although in my opinion the best way will be to have a Grid with n*n row and columns and add your buttons to respective row, columns.

Giving SelectedIndex -1 in Listbox

I'm having listbox and getting selected item. The code is
private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = listBox1.SelectedIndex;
string str ="";
if (index == 0)
{
str = (string)Text1.Text;
}
else if (index == 1)
{
str = (string)Text2.Text;
}
else if (index == 2)
{
str = (string)Text3.Text;
}
MessageBox.Show(str);
// listBox1.SelectedIndex = -1;
}
By this above code, after I selected one item,blue color bar is displaying in selected item, to avoid that I gave listBox1.SelectedIndex = -1;.
If I gave this this function is get executed twice.and the message box shows empty string.
Why is it happening? How can I avoid that?
try construction like this:
listBox1.SelectionChanged -= listBox1_SelectionChanged;
listBox1.SelectedIndex = -1;
listBox1.SelectionChanged += listBox1_SelectionChanged;
listBox1.SelectedIndex is calling SelectionChanged event so this function is called twice.

Resources