Selecting an item in a combo cause a cyclic call - c#

I am writing a winform application.
On the form I have a Label and a combobox.
The combobox is populated with fonts names. When selecting a font from it, the label's font text is changing accordingly. When clicking on the label, the combobox's selecteditem is set accordingly.
The problem is when I am selecting the label, I get a cyclic call: I am setting the combobox item according to the font of the label, then the SelectedIndexChanged is fired which is in charge to set the font name of the label (ChangeLabelFont).
So the label font is again updated while it is the trigger of all those calls.
private void FontToolStripComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (_selectedLabel == null)
{
return;
}
ChangeLabelFont(FontNameToolStripComboBox.SelectedItem.ToString(), FontSizeToolStripComboBox.SelectedItem.ToString());
}
private void SelectLabel(Point location)
{
SetComboBoxesFont(_selectedLabel.Font.Name, _selectedLabel.Font.Size);
}
private void SetComboBoxesFont(string name, float size)
{
FontNameToolStripComboBox.SelectedItem = name;
FontSizeToolStripComboBox.SelectedItem = size;
}
private void ChangeLabelFont(string name, string size)
{
if (_selectedLabel == null)
{
return;
}
FontFamily fontFamily = new FontFamily(name);
float fontSize = float.Parse(size);
_selectedLabel.Font = new Font(fontFamily, fontSize);
}
Is there a way to prevent this cyclic call? Maybe I am doing something wrong?

There is something missing in the code you're showing. I'd like to see the ChangeLabelFont method as well as the handler for the "select label" event. But I suppose it's the SelectLabel method in the end. So how about using a member flag:
private bool m_bInhibitCycle = false;
private void SelectLabel(Point location)
{
if (m_bInhibitCycle) return;
m_bInhibitCycle = true;
SetComboBoxesFont(_selectedLabel.Font.Name, _selectedLabel.Font.Size);
m_bInhibitCycle = false;
}

Related

Is select the one subitem without full row possible? C# [duplicate]

I am trying to get the selected ListViewItem index, so that when the user clicks on a particular row, such as row 2, I want the clicked cell's text to be set to a TextBox's text.
I also want to highlight only this cell, ideally using the regular selection ListView uses, or do I need to create a class that inherits from ListView to do this?
Something like this:
You can draw yourself the ListViewItem.ListViewSubItem selected, owner-drawing the Control (set ListView.OwnerDraw = true), then handle the ListView.DrawSubItem event.
The ListView.DrawColumnHeader event can use default values.
▶ I'm using TextRenderer since this is the default renderer. If you use Graphics.DrawText, you'll notice the difference.
TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding |
TextFormatFlags.VerticalCenter;
private void listView1_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
var lv = sender as ListView;
var subItem = lv.HitTest(lv.PointToClient(MousePosition)).SubItem;
if (subItem != null && e.SubItem == subItem) {
using (var brush = new SolidBrush(SystemColors.Highlight)) {
e.Graphics.FillRectangle(brush, e.SubItem.Bounds);
}
TextRenderer.DrawText(e.Graphics, e.SubItem.Text, e.SubItem.Font,
e.Bounds, SystemColors.HighlightText, flags);
}
else {
e.DrawDefault = true;
}
}
private void listView1_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
=> e.DrawDefault = true;
// Invalidate on a mouse interaction, otherwise the ListView doesn't redraw the SubItem
private void listView1_MouseUp(object sender, MouseEventArgs e)
=> (sender as ListView).Invalidate();
Or, you can change the Colors of a SubItem when a mouse interaction is notified (here, using the MouseDown event) and save the previous state (just the Colors here). It's better to save the state because each SubItem can have it's own settings, so you cannot just revert back to the Parent ListViewItem or the ListView values.
As mentioned, set UseItemStyleForSubItems = false in each parent ListViewItem, otherwise the Colors settings are ignored.
Also, FullRowSelect must be set to false, otherwise it's pointless :)
Here, the state is saved in a nullable named Tuple Field, (ListViewSubItem, Color[]).
A class object is probably better, this is just shorter.
private (ListViewItem.ListViewSubItem Item, Color[] colors)? previousItem = null;
private void listView1_MouseDown(object sender, MouseEventArgs e)
{
var lv = sender as ListView;
var subItem = lv.HitTest(e.Location).SubItem;
if (previousItem.HasValue) {
// If an Item's Colors have been changed, restore the state
// It removes the selection if you click in an empty area
previousItem.Value.Item.BackColor = previousItem.Value.colors[0];
previousItem.Value.Item.ForeColor = previousItem.Value.colors[1];
lv.Invalidate(previousItem.Value.Item.Bounds);
}
if (subItem != null) {
// Save the SubItem's colors state
previousItem = (subItem, new[] { subItem.BackColor, subItem.ForeColor });
// Set new Colors. Here, using the default highlight colors
subItem.BackColor = SystemColors.Highlight;
subItem.ForeColor = SystemColors.HighlightText;
lv.Invalidate(subItem.Bounds);
}
}
This is how this thing works:
▶ About the Item / SubItem index, as it's mentioned in the question:
When you retrieve the ListViewItem / SubItem clicked with ListView.HitTest
var hitTest = lv.HitTest(e.Location);
then the ListViewItem index is of course:
var itemIndex = hitTest.Item.Index;
and the SubItem.Index is:
var subItemIndex = hitTest.Item.SubItems.IndexOf(hitTest.SubItem);

C# Change text on click event

I have a field that currently changes text on a mouse click. This field is on multiple pages of my report, but clicking it only changes the text for that page. Here is my code for that:
const string sShowDetail2 = "Show Cumulative";
const string sHideDetail2 = "Hide Cumulative";
ArrayList expandedValues2 = new ArrayList();
// This function returns a value indicating whether a certain
// category's details should be expanded.
bool ShouldShowDetail2(int CategoryID)
{
return expandedValues2.Contains(CategoryID);
}
private void lbShowHide2_BeforePrint(object sender, PrintEventArgs e)
{
XRLabel label = (XRLabel)sender;
if (ShouldShowDetail2((int)label.Tag))
{
label.Text = sHideDetail2;
}
else
{
label.Text = sShowDetail2;
}
}
private void lbShowHide2_PreviewClick(object sender, PreviewMouseEventArgs e)
{
// Obtain the category's ID stored in the label's Tag property.
int index = (int)e.Brick.Value;
// Determine whether the current category's details are shown.
bool showDetail2 = ShouldShowDetail2(index);
// Toggle the visibility of the category's details.
if (showDetail2)
{
expandedValues2.Remove(index);
}
else
{
expandedValues2.Add(index);
}
Now I have another field that I need to simply toggle all instances of the field(every page) on click. I do not need it to read the tag of the particular one I clicked because it will be changing all of them at the same time. My problem is that I don't know how to get them all to change on mouse click. How could I modify the code above to change all instances of the label?
Here is something you could try
private void ChangeXRLabels(Control control)
{
foreach(Control childControl in control.Controls)
{
XRLabel label = childControl as XRLabel;
if(label != string.Empty)
label.Text = "Your Text Value goes Here";
else if(childControl.Controls.Count > 0)
ChangeXRLabels(childControl);
}
}

Change Telerik GricViewComboBox Append List Font

i have a Telerik GridView with a ComboBox Column, while filtering in this Combobox an appending list dropped down.
Same as the image below...
So i want to make the font of the Append list larger.
How to do it?
RadDropDownList uses different popups for its default items representation and for its auto complete suggest items. Here is an example demonstrating how to change the font of both:
void radGridView1_CellEditorInitialized(object sender, GridViewCellEventArgs e)
{
RadDropDownListEditor editor = e.ActiveEditor as RadDropDownListEditor;
if (editor != null)
{
editor.DropDownStyle = RadDropDownStyle.DropDown;
RadDropDownListEditorElement element = (RadDropDownListEditorElement)editor.EditorElement;
element.VisualItemFormatting -= element_VisualItemFormatting;
element.AutoCompleteSuggest.DropDownList.VisualItemFormatting -= element_VisualItemFormatting;
//this handles the default drop down formatting - when you press the arrow key to open the drop down
element.VisualItemFormatting += element_VisualItemFormatting;
//this handles the suggest popup formatting
element.AutoCompleteSuggest.DropDownList.VisualItemFormatting += element_VisualItemFormatting;
}
}
void element_VisualItemFormatting(object sender, VisualItemFormattingEventArgs args)
{
args.VisualItem.Font = new Font("Arial", 16);
}
You could either make a CellTemplate with your font, or you could handle the CellFormating event and do something like this:
void radGridView_CellFormatting(object sender, CellFormattingEventArgs e)
{
// For all cells under the Account Name column
if(e.CellElement.ColumnInfo.Name == "Account Name")
{
if(e.CellElement.Value != null)
{
System.Drawing.Font newfontsize = new System.Drawing.Font(e.CellElement.Font.FontFamily.Name,20);
for each(GridViewCellInfo cell in e.Row.Cells)
{
e.CellElement.Font = newfontsize;
}
}
}
// For all other cells under other columns
else
{
e.CellElement.ResetValue(Telerik.WinControls.UI.LightVisualElement.Font, Telerik.WinControls.ValueResetFlags.Local);
}
}
Plug in whatever size font you want for the "newfontsize" variable. Also note that, the else statement may not be necessary in your case, but you can reset the font to default using the ResetValue property.

How to extract font size of content

I've been working on making my own little text editor using a RichTextBox(MyRTB). I've made a Combobox to change the font of the selected text within the RichTextBox when the value changes using this code block:
private void CmbFont_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MyRTB != null)
{
string fontsize = (((ComboBoxItem)CmbFont.SelectedItem).Content).ToString();
MyRTB.Selection.ApplyPropertyValue(Run.FontSizeProperty, fontsize);
}
}
Now I'd like my Combobox value to change every time I select a string of text within the RichTextBox that has a different font size. Is this possible?
Thanks
Add an event handler to the selection changed event. In that event handler get the TextElement.FontSizeProperty from the RichTextBox selection
...
MyRTB.SelectionChanged += OnSelectionChanged;
...
void OnSelectionChanged()
{
var fontSize = MyRTB.Selection.GetPropertyValue(TextElement.FontSizeProperty);
if (fontSize == DependencyProperty.UnsetValue)
{
// Selection has text with different font sizes.
}
else {
// (double)fontSize is the current font size. Update Cmb_Font..
}
}
Make sure you don't call OnSelectionChanged & CmbFont_SelectionChanged recursively.

WPF: Dropdown of a Combobox highlightes the text

When I type in the combobox I automatically opens enables the dropdown list
searchComboBox.IsDropDownOpen = true;
The problem here is - the text gets highlighted and the next keystrock overwrites the previous text.
How can I disable the text highlighting when ComboBox DropDown opens up?
I had this very same issue and like some of the users being new to WPF, struggled to get the solution given by Einar Guðsteinsson to work. So in support of his answer I'm pasting here the steps to get this to work. (Or more accurately how I got this to work).
First create a custom combobox class which inherits from the Combobox class. See code below for full implementation. You can change the code in OnDropSelectionChanged to suit your individual requirements.
namespace MyCustomComboBoxApp
{
using System.Windows.Controls;
public class MyCustomComboBox : ComboBox
{
private int caretPosition;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
{
TextBox txt = (TextBox)sender;
if (base.IsDropDownOpen && txt.SelectionLength > 0)
{
caretPosition = txt.SelectionLength; // caretPosition must be set to TextBox's SelectionLength
txt.CaretIndex = caretPosition;
}
if (txt.SelectionLength == 0 && txt.CaretIndex != 0)
{
caretPosition = txt.CaretIndex;
}
}
}
Ensure that this custom combo class exists in the same project. THen you can use the code below to reference this combo in your UI.
<Window x:Class="MyCustomComboBoxApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:MyCustomComboBoxApp"
Title="MainWindow" Height="350" Width="525" FocusManager.FocusedElement="{Binding ElementName=cb}">
<Grid>
<StackPanel Orientation="Vertical">
<cc:MyCustomComboBox x:Name="cb" IsEditable="True" Height="20" Margin="10" IsTextSearchEnabled="False" KeyUp="cb_KeyUp">
<ComboBoxItem>Toyota</ComboBoxItem>
<ComboBoxItem>Honda</ComboBoxItem>
<ComboBoxItem>Suzuki</ComboBoxItem>
<ComboBoxItem>Vauxhall</ComboBoxItem>
</cc:MyCustomComboBox>
</StackPanel>
</Grid>
</Window>
Thats it! Any questions, please ask! I'll do my best to help.
THanks to Einar Guðsteinsson for his solution!
Better late than never and if some one else hit this proplem he might use this.
There is away todo this if you override combobox.
First get handle on the textbox that is used in the template and register to selectionchanged event.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, RoutedEventArgs e)
{
// Your code
}
Then in the event handler you can set the selection again like you want it to be. In my case I was calling IsDropDownOpen in code. Saved selection there then put it back in the event handler. Ugly but did the trick.
Further to clsturgeon's answer, I have solved the issue by setting the selection when DropDownOpened event occurred:
private void ItemCBox_DropDownOpened(object sender, EventArgs e)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
I think in the Solution provided by Andrew N there is something missing as when I tried it out the Selection Changed event of the TextBox was placing the caret at the wrong place. So I made this change to solve that.
namespace MyCustomComboBoxApp { using System.Windows.Controls;
public class MyCustomComboBox : ComboBox
{
private int caretPosition;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var element = GetTemplateChild("PART_EditableTextBox");
if (element != null)
{
var textBox = (TextBox)element;
textBox.SelectionChanged += OnDropSelectionChanged;
}
}
private void OnDropSelectionChanged(object sender, System.Windows.RoutedEventArgs e)
{
TextBox txt = (TextBox)sender;
if (base.IsDropDownOpen && txt.SelectionLength > 0)
{
caretPosition = txt.SelectionLength; // caretPosition must be set to TextBox's SelectionLength
txt.CaretIndex = caretPosition;
}
if (txt.SelectionLength == 0 && txt.CaretIndex != 0)
{
caretPosition = txt.CaretIndex;
}
}
}
When a comboxbox gains focus you can disable the text highlighting (i.e. by selecting no text upon the GotFocus event). However, when you pulldown the combobox the system is going to locate the item in the list and make that the selected item. This in turn automatically highlights the text. If I understand the behaviour you are looking for, I do not believe it is fully possible.
I was able to fix it using a modified answer from Jun Xie. Assuming you are using the keyUp event for your combobox search, I found an edge case in my custom use case that would still overwrite the text:
Type in the combobox for the first time. Text is fine.
Use up and down arrow keys to select an item in the list, but not "committing" the change (pressing enter, for example, and closing the dropDown selections. Note the text is highlighted at this point like clsturgeon points out.
Try to type in the textbox again. In this case the text will be over-ridden because the dropdown was still open hence the event never fired to clear the highlight.
The solution is to check if an item is selected. Here's the working code:
XAML:
<ComboBox x:Name="SearchBox" IsEditable="True" KeyUp="SearchBox_KeyUp"
PreviewMouseDown="SearchBox_PreviewMouseDown" IsTextSearchEnabled="False"
DropDownOpened="SearchBox_DropDownOpened">
</ComboBox>
Code:
private void SearchBox_KeyUp(object sender, KeyEventArgs e)
{
SearchBox.IsDropDownOpen = true;
if (e.Key == Key.Down || e.Key == Key.Up)
{
e.Handled = true;
//if trying to navigate but there's noting selected, then select one
if(SearchBox.Items.Count > 0 && SearchBox.SelectedIndex == -1)
{
SearchBox.SelectedIndex = 0;
}
}
else if (e.Key == Key.Enter)
{
//commit to selection
}
else if (string.IsNullOrWhiteSpace(SearchBox.Text))
{
SearchBox.Items.Clear();
SearchBox.IsDropDownOpen = false;
SearchBox.SelectedIndex = -1;
}
else if (SearchBox.Text.Length > 1)
{
//if something is currently selected, then changing the selected index later will loose
//focus on textbox part of combobox and cause the text to
//highlight in the middle of typing. this will "eat" the first letter or two of the user's search
if(SearchBox.SelectedIndex != -1)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
//backup what the user was typing
string temp = SearchBox.Text;
//set the selected index to nothing. sets focus to dropdown
SearchBox.SelectedIndex = -1;
//restore the text. sets focus and highlights the combobox text
SearchBox.Text = temp;
//set the selection to the end (remove selection)
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
//your search logic
}
}
private void SearchBox_DropDownOpened(object sender, EventArgs e)
{
TextBox textBox = (TextBox)((ComboBox)sender).Template.FindName("PART_EditableTextBox", (ComboBox)sender);
textBox.SelectionStart = ((ComboBox)sender).Text.Length;
textBox.SelectionLength = 0;
}
An alternative. Prevent the framework from messing with selection.
public class ReflectionPreventSelectAllOnDropDown
{
private static readonly PropertyInfo edtbPropertyInfo;
static ReflectionPreventSelectAllOnDropDown()
{
edtbPropertyInfo = typeof(ComboBox).GetProperty("EditableTextBoxSite", BindingFlags.NonPublic | BindingFlags.Instance);
}
public void DropDown(ComboBox comboBox)
{
if (!comboBox.IsDropDownOpen)
{
var edtb = edtbPropertyInfo.GetValue(comboBox);
edtbPropertyInfo.SetValue(comboBox, null);
comboBox.IsDropDownOpen = true;
edtbPropertyInfo.SetValue(comboBox, edtb);
}
}
}

Categories