I need to show items in a combobox with a different background color. I also want to change what that color is depending on if the item is selected (or the mouse is on top of it), just the same way it works when a combobox is not owner-drawn.
It is all working fine, except that when the mouse comes off one of the items that I changed the color for, the item keeps the same color as when the mouse was on top. In the example below, the item 'other' is initially correctly drawn with myUnselectedBrush; the mouse goes over top, it is correctly drawn with mySelectedBrush; when the mouse comes off, it is incorrectly still drawn with mySelectedBrush; it should have been drawn with myUnselectedBrush. Everything works fine for item 'something', whose color is not altered.
What am I doing wrong?
private void comboBoxDraw(object sender, DrawItemEventArgs e)
{
ComboBox cb = (ComboBox)sender;
Graphics g = e.Graphics;
e.DrawBackground();
if (e.Index > -1)
{
object item = cb.Items[e.Index];
switch (somethingOrOther)
{
case something:
break;
case other:
e.Graphics.FillRectangle(
(cb.SelectedIndex == e.Index)
? mySelectedBrush
: myUnselectedBrush,
e.Bounds);
break;
}
}
}
e.DrawFocusRectangle();
if (e.Index > -1)
{
// draw the string
}
}
Rather than using
cb.SelectedIndex == e.Index
I needed to use DrawItemState:
((state & DrawItemState.Selected) > 0) || ((state & DrawItemState.HotLight) > 0)
Related
I am trying to implement very simple text formatting functionality for a RichTextBox in WPF. This just consists of a few bold, italic, etc ToggleButtons just above the RichTextBox. See image below, but ignore the top TextBox - the RichTextBox is the bigger one at the bottom.
Toggling formatting for either a selection or at the caret position (for text that will be typed in) is not a problem, as I'm doing this:
private void BoldButton_Checked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(true);
}
private void BoldButton_Unchecked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(false);
}
private void SetSelectionBold(bool isBold)
{
var selection = this.RichText.Selection;
if (selection != null)
{
selection.ApplyPropertyValue(TextElement.FontWeightProperty, isBold ? FontWeights.Bold : FontWeights.Normal);
}
}
However, if the user moves the caret somewhere else (e.g. from bold text to normal text) then I'd like the ToggleButtons to reflect that state, in much the same way as it works in Word. Is it possible to detect when the caret position changes, and take action accordingly?
Hook yourself into SelectionChanged event and get current caret position, and test if the property exists on that selection?
In the event, probably you want something like:
var selection = richTextBox.Selection;
if(selection != null)
{
if(selection.GetPropertyValue(TextElement.FontWeightProperty) == FontWeights.Bold)
// todo; enable your button
}
If that event is not triggered by caret positioning(the document doesn't say anything about that),
you probably need to inherit from RichTextBox and override OnSelectionChanged, after that you need to actually generate your own Caret, eg:
var currentCaretPlusOne = new TextRange(richTextBox.CaretPosition,
richTextBox.CaretPosition+1);
if(currentCaretPlusOne != null)
{
if(currentCaretPlusOne.GetPropertyValue(TextElement.FontWeightProperty)
== FontWeights.Bold)
// todo; enable your button
}
I have a toolbar which contains several controls, say 10.
I want to place last three controls in the right corner of the tool bar with some space between these three controls and remaining controls.
When I resize the window, it should reduce the space, once there is no space it should be cliped to overflow drop down one by one.
Note: I need to use only one toolbar.
I did this once before
The items existed in a customized ScrollViewer so they could go off-screen, and the SizeChanged event of the ScrollViewer would check each item to see if it was off-screen or not. If it wasn't visibile, or was partially visible, then the item would get hidden and the data object would get added to a Menu. If the item was visible, it would remove it from the Menu if it existed there, and set it visible. The Menu was only visible if it had items.
This was done a while back when I was still new to WPF, so I'm sure the process could be improved on.
The helper class I used to determine item visibility is listed below
public static ControlVisibility IsObjectVisibleInContainer(FrameworkElement child, UIElement parent)
{
GeneralTransform childTransform = child.TransformToAncestor(parent);
Rect childSize = childTransform.TransformBounds(new Rect(new Point(0, 0), new Point(child.Width, child.Height)));
Rect result = Rect.Intersect(new Rect(new Point(0, 0), parent.RenderSize), childSize);
if (result == Rect.Empty)
{
return ControlVisibility.Hidden;
}
else if (result.Height == childSize.Height && result.Width == childSize.Width)
{
return ControlVisibility.Full;
}
else if (result.Height == childSize.Height)
{
return ControlVisibility.FullHeightPartialWidth;
}
else if (result.Width == childSize.Width)
{
return ControlVisibility.FullWidthPartialHeight;
}
else
{
return ControlVisibility.Partial;
}
}
public enum ControlVisibility
{
Hidden,
Partial,
Full,
FullHeightPartialWidth,
FullWidthPartialHeight
}
It could be used like this:
ControlVisibility itemVisibility =
MyHelpers.IsObjectVisibleInContainer(someItem, parentContainer);
I have a ListBox that displays some words. Words are entered in TextBox, and when submitted on button click, they are added to ListBox. The problem is, if I add many words, scroll is always on top of ListBox, so I don't see last but first words added. Is there a way to dynamically move scroll to the end of ListBox every time word is added, so last added word is visible?
here you go, this should do nicely...
public static void ScrollToBottom(this ListBox listbox)
{
if (listbox == null) throw new ArgumentNullException("listbox", "Argument listbox cannot be null");
if (!listbox.IsInitialized) throw new InvalidOperationException("ListBox is in an invalid state: IsInitialized == false");
if (listbox.Items.Count == 0)
return;
listbox.ScrollIntoView(listbox.Items[listbox.Items.Count - 1]);
}
Now, given any ListBox I can do this:
ListBox lb = ...;
lb.ScrollToBottom();
how can I change the font for a currently selected text area inside a WPF RichTextBox?
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}
How about something like:
TextSelection text = richTextBox.Selection;
if (!text.IsEmpty)
{
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
Solved...
if (this.TextEditor.Selection.IsEmpty)
this.TextEditor.CurrentFontFamily = SelectedFont;
else
this.TextEditor.Selection.ApplyPropertyValue(TextElement.FontFamilyProperty, SelectedFont);
To get the current selection use:
Dim rng As TextRange = New TextRange(YourRtfBox.Selection.Start, YourRtfBox.Selection.End)
And then set the fontstyle:
rng.ApplyPropertyValue(Inline.FontSizeProperty, YourFontSizeValue)
rng.ApplyPropertyValue(Inline.FontFamilyProperty, YourFontFamilyValue)
To change the font family for a selection in the RichTextBox you should use this:
text.ApplyPropertyValue(Run.FontFamilyProperty, value);
The selected text in a RichTextBox is a Run object, so one must use the Run Dependency Properties.
This seems to work in Silverlight at least, so should be the same thing in WPF.
Is there a way to make some of the items in a ListBox readonly/disabled so they can't be selected? Or are there any similar controls to ListBox to provide this functionality?
ListBox doesn't have support for that. You can bolt something on, you could deselect a selected item. Here's a silly example that prevents even-numbered items from being selected:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) {
for (int ix = listBox1.SelectedIndices.Count - 1; ix >= 0; ix--) {
if (listBox1.SelectedIndices[ix] % 2 != 0)
listBox1.SelectedIndices.Remove(listBox1.SelectedIndices[ix]);
}
}
But the flicker is quite noticeable and it messes up keyboard navigation. You can get better results by using CheckedListBox, you can prevent the user from checking the box for an item:
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
if (e.Index % 2 != 0) e.NewValue = CheckState.Unchecked;
}
But now you cannot override drawing to make it look obvious to the user that the item isn't selectable. No great solutions here, it is far simpler to just not display items in the box that shouldn't be selectable.
#Hans solution causing that the item id selected for a short time and then selection disappearing. I don't like that - this can be confusing for the enduser.
I prefer to hide some edit option buttons for the item that should be disabled:
if (lbSystemUsers.Items.Count > 0 && lbSystemUsers.SelectedIndices.Count > 0)
if (((RemoteSystemUserListEntity)lbSystemUsers.SelectedItem).Value == appLogin)
{
bSystemUsersDelete.Visible = false;
bSystemUsersEdit.Visible = false;
}
else
{
bSystemUsersDelete.Visible = true;
bSystemUsersEdit.Visible = true;
}
Here is the list that lists the users and disallow to edit user that is actually logged in to the edit panel.
ListBox doesn't have a ReadOnly (or similar) property, but you can make a custom ListBox control. Here's a solution that worked pretty well for me:
https://ajeethtechnotes.blogspot.com/2009/02/readonly-listbox.html
public class ReadOnlyListBox : ListBox
{
private bool _readOnly = false;
public bool ReadOnly
{
get { return _readOnly; }
set { _readOnly = value; }
}
protected override void DefWndProc(ref Message m)
{
// If ReadOnly is set to true, then block any messages
// to the selection area from the mouse or keyboard.
// Let all other messages pass through to the
// Windows default implementation of DefWndProc.
if (!_readOnly || ((m.Msg <= 0x0200 || m.Msg >= 0x020E)
&& (m.Msg <= 0x0100 || m.Msg >= 0x0109)
&& m.Msg != 0x2111
&& m.Msg != 0x87))
{
base.DefWndProc(ref m);
}
}
}
I know this is old thread, but i'll post a workaround for other readers in future.
listBox.Enabled = false;
listBox.BackColor = Color.LightGray;
This will change background color of list box to Light Gray. So this is not builtin "native way" to do it, but at least gives user some feedback that he is not supposed to / can't edit that field.
To get read-only behaviour I have MyCBLLocked, a boolean associated with the MyCBL checkbox list control, and on the CheckItem event I do:
private void MyCBL_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (MyCBLLocked)
e.NewValue = e.CurrentValue;
}
So instead of
MyCBL.Enabled = false;
I use
MyCBLLocked = true;
and the user can scroll through the many selections but not mess things up with changes.