Universal Selection Color Change WinForms Controls - c#

Overview
I am currently working on a theme system for my application allowing Light and Dark themes for users to choose from (similar to visual studio). The entire process is pretty straight forward so far; however there are a few things that I'm still having a couple issues (well, more of wanting to know if there are better ways to do it). During the process of development I have put in quite a bit of research into changing the selection color of controls that support it (ListView, ComboBox, ListBox, TextBox, RichTextBox, etc). I have found that the most straight forward way of accomplishing this (especially with list style controls) is to perform custom drawing of the items by utilizing the events available for each control. I know that RichTextBox has the SelectionBackColor property that will allow you to change it's selection color, however the TextBox control does not offer this same property, nor anything similar that I have found.
With the ListBox control, it does not contain any sort of selection color property for further customization, meaning I have to utilize OwnerDraw and the DrawItem/DrawSubItem events to custom paint the back color while setting the HideSelection property to false to help with the change when focus is lost.
The ComboBox and ListBox controls are simple enough to change by utilizing the DrawItem event and setting it up for OwnerDraw like the ListBox control.
As we all know there are many different controls that support user selection, and I could go on and on into my research, but I think what I have supplied is enough to help describe why I am here.
The Question
Since the code to manipulate all of this via a single class is seriously tedious, and restrictive to future developers (especially if they are performing custom drawing of items in these controls). I am wondering if there is a simplified way to manipulate just the selection color. I have already found a post here on StackOverflow that utilizes DllImport to manipulate the system highlight color; however, changing things at the OS level doesn't seem like a smart, nor safe thing to do for something so simple. I am just as reluctant to manipulate system code as I am to blindly manipulate kernel registers in MIPS Assembly.
The post I found on StackOverflow utilized:
[DllImport("user32.dll")]
static extern bool SetSysColors(int cElements, int[] lpaElements, uint[] lpaRgbValues);
void ChangeSelectColour(Color color) {
const int COLOR_HIGHLIGHT = 13;
const int COLOR_HIGHLIGHTTEXT = 14;
// You will have to set the HighlightText colour if you want to change that as well.
//array of elements to change
int[] elements = { COLOR_HIGHLIGHT };
List<uint> colours = new List<uint>();
colours.Add((uint)ColorTranslator.ToWin32(color));
//set the desktop color using p/invoke
SetSysColors(elements.Length, elements, colours.ToArray());
}
Is this the only way of doing this? If so I can abandon the idea of changing the selection color throughout the application since this doesn't seem too safe (unless someone can prove otherwise).
My current implementation (the hard way referred to in the overview section) is like this (just a small sample):
public static void ApplyStyles(Control c) {
if (c is Button) {
Button b = (Button)c;
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
}
if (c is RoundedPanel || c is PictureBox) {
// Do nothing.
} else {
if (c is Label) {
if (c.Parent is RoundedPanel) {
// Do nothing.
} else {
c.BackColor = BackColor;
c.ForeColor = ForeColor;
}
} else {
c.BackColor = BackColor;
c.ForeColor = ForeColor;
}
if (c is ListView) {
ListView lv = (ListView)c;
// PSEUDO BEGIN
lv.OwnerDraw = true;
lv.DrawItem += DrawListViewItem;
lv.DrawSubItem += DrawListViewSubItem;
// PSEUDO END
if (Style = Themes.Dark)
lv.GridLines = false;
foreach (ListViewItem lvi in lv.Items) {
lvi.BackColor = BackColor;
lvi.ForeColor = ForeColor;
}
}
}
}
Now if you could imagine having to do that for every type that supports user selection, you end up with several conditions on a generic type that then create custom events to paint the items contained inside the control, which can get even more difficult when you start looking into the support for images, check boxes, so on, and so forth. This code can expand far and wide pretty rapidly if there aren't any generic methods to change the selection color, and in my case wouldn't be worth the effort to do (unless I made it a very low priority and worked on it occasionally through the years).
Also, keep in mind when you make suggestions; I work in a government environment so third party products all have to be approved, and in my case, a 100% solid reason as to why we need that product would have to be supplied for approval so third party theming products wouldn't really help in my case.
I thank you all for taking the time to read this, and if anyone has any ideas, please feel free to let me know!
NOTE
If you feel I left out any needed information, or if I should be any clearer on certain things, feel free to comment and let me know so that I can update the clarity of the post for future readers.
References
RichTextBox.SelectionBackColor
Use SelectionBackColor to get or set the color of selected text in the RichTextBox. If no text is currently selected, the SelectionBackColor property applies to the current position of the caret. Characters that are entered from that position have the specified SelectionBackColor.
ComboBox.DrawItem
This event is used by an owner-drawn ComboBox. You can use this event to perform the tasks needed to draw items in the ComboBox.
ListBox.DrawItem
This event is used by an owner-drawn ListBox. The event is only raised when the DrawMode property is set to DrawMode.OwnerDrawFixed or DrawMode.OwnerDrawVariable. You can use this event to perform the tasks needed to draw items in the ListBox.
Additional Links
ComboBox.DrawItem Event
ListBox.DrawItem Event

Related

MetroFramework for windows forms project and tab control rendering issue

I'm not sure where to begin here in order to troubleshoot and work on the 3rd party code to try and fix this so I thought I would ask here and show a screen of the issue. I am using a third party windows form "metro controls" package from https://thielj.github.io/MetroFramework/ and it looks a bit dated and no longer maintained and the only real issue I am having is that the tab control specifically seems to have extra rendering "junk" that goes away when you click it. But I don't know why it's drawn in the first place or how to go about removing / fixing it and updating the package on github. The UI package is useful it makes a plain windows form project look much nicer and have a far better UI so that's the reason I picked it up.
Is anyone familiar with this library and having similar issues or know the best way to approach fixing it myself? Keep in mind I am not familiar with the drawing/gui portion of windows forms at all and usually reference online material when it comes to drawing or UI type work.
Turned out I found this library instead (slightly newer / more updates) https://www.nuget.org/packages/MetroModernUI/ but still has some similar issues I am having however I modified the code slightly to help accommodate one of the issues I am having, the OnDrawItem event lifecycle seems to be gone for setting OwnerDrawFixed property on a tab control.
Here is the code I modified in MetroTabControl.cs and I am just using a local version/build to support any customizations and enhancements I have to do since there is a few other problems I have as well.
private void DrawTab(int index, Graphics graphics)
{
Color foreColor;
Color backColor = BackColor;
if (!useCustomBackColor)
{
backColor = MetroPaint.BackColor.Form(Theme);
}
TabPage tabPage = TabPages[index];
Rectangle tabRect = GetTabRect(index);
if (!Enabled || tabDisable.Contains(tabPage.Name))
{
foreColor = MetroPaint.ForeColor.Label.Disabled(Theme);
}
else
{
if (useCustomForeColor)
{
foreColor = DefaultForeColor;
}
else
{
foreColor = !useStyleColors ? MetroPaint.ForeColor.TabControl.Normal(Theme) : MetroPaint.GetStyleColor(Style);
}
}
if (index == 0)
{
tabRect.X = DisplayRectangle.X;
}
Rectangle bgRect = tabRect;
tabRect.Width += 20;
using (Brush bgBrush = new SolidBrush(backColor))
{
graphics.FillRectangle(bgBrush, bgRect);
}
TextRenderer.DrawText(graphics, tabPage.Text, MetroFonts.TabControl(metroLabelSize, metroLabelWeight),
tabRect, foreColor, backColor, MetroPaint.GetTextFormatFlags(TextAlign));
//HACK not properly handling the owner draw fixed event/override life cycle so we can fire it ourselves and if something is listening
// it will execute
if (this.DrawMode == TabDrawMode.OwnerDrawFixed)
{
OnDrawItem(new DrawItemEventArgs(graphics, this.Font, tabRect, index, DrawItemState.None));
}
}
For the other UI issue I am tracking down where I can invalidate or fix the odd additional button / clickable disappearing areas in the tab control or panels for this UI enhancement framework.
What happens is that there should be some error related to the Size of the MetroTabControl, and you can fix that with the following code:
this.metroTabPage1.VerticalScrollbarBarColor = false;
this.metroTabPage1.VerticalScrollbarSize = 0;

Drag and drop event. Get the name of the control where the drag originated, whilst inside drop event method

I am attempting a drag and drop in WPF.
My program allows you to drag coloured labels around the screen, in essence giving you the effect that squares are being dragged and dropped.
Bearing in mind that only the text is dragged rather then the control itself (i.e. not the colour):
What I would like to achieve is that when the drop event fires, I can change the colour of the label which I dragged the text from.
After consulting MSDN I've failed to figure out how to get at the control in question and after plenty of trial and error I'm hoping somebody here can help. https://msdn.microsoft.com/en-us/library/system.windows.forms.drageventargs.data(v=vs.110).aspx
Below is a sample of code which works, but the label who's colour I want to change is hard-coded, whereas in reality it could be any one of a number of labels.
private void ObjDrop(object sender, DragEventArgs e)
{
//testSquare is a hardcoded label
testSquare.Background = Brushes.LimeGreen;
//what I really need is for a variable to detect which label to access each time before I change its colour. So something along the lines of
Label myLabel = someCodeToGetTheLabelThatWasDragged;
myLabel.Background = Brushes.LimeGreen;
}
Hopefully I explained things well enough, thanks in advance.
You would use the IDataObject.GetData(Type) method to extract the the object in DragEventArgs.Data property. From there, you should be able to access whatever you store in the IDataObject.
This is a pretty general answer. To achieve said answer, that means you'll have to write your own class that implements IDataObject which contains the original control/control's name etc., then set the IDataObject when drag in initialized.
There may be an alternate solution available. I would watch what e.Data is in your current example, and try to work with that. If e.Data is of type Label, through casting, you could access the label that way, e.g. (e.Data as Label).Background = Brushes.LimeGreen;.
In a Drag and Drop implementation I've seen, the IDataObject contains the DropTarget and the DragSource, that way you can compare the two and allow/disallow things/types from being dragged and dropped by setting the Effect.
This page provides the solution for what i'm looking to do.
WPF Drag and Drop - Get original source info from DragEventArgs
The following code in particular used in the drop event method allowed me to achieve my aim
Label lbl = e.Data.GetData("System.Windows.Controls.Label") as Label;
After that I could manipulate the source of the drag whatever way I wished.

Apply Input Mask for a ToolStripComboBox C#

I was just wondering if anyone has come across how to apply an input mask for a Tool Strip Combo Box in C#?
My drop down box gets populated over time with IP addresses, and I would like to restrict what the users are able to write in (Ex: can only input 3 '.'s, only numbers, etc).
So if anyone is able to help out I would really appreciate it!
Thanks in advance.
Edit
My design has changed so I now need to have a ToolStripComboBox
You could try catching the KeyUp event, then check that the input is valid. If not revert it to the last valid input. You would probably want to do something similar with the Validating event (make sure CausesValidation is true).
Another option would be to create a MaskedTextBox and place it so it covers the text box portion of the drop down menu. You would then need to wire up the events so the two form controls remained synced.
You could also look into the ErrorProvider class.
There are a couple of other ways (like a timer which runs ever .3 seconds), but those are usually performance hogs or difficult to maintain.
Update for regular expression comment:
If I was to do this I might use a regular expression or I might manually parse the string.
Either way the KeyUp and Validating events is where I would check the validation of the control. The KeyUp event gives me the option to check as they type while the Validating event allows me to validate when the control loses focus. Which you use will depend on what you want the user experience to be.
If you do not use the KeyUp event to validate, you could add a timer which runs 5 seconds after the last key press. This way the control would not have to lose focus for the error to show.
Update for edited question and comment:
You could not use Format event as your question was on how to format user input, not how things are added to the list. As such that solution does not work with ToolStripComboBox or with ComboBox.
After reading the documentation for ToolStripControlHost, you might be able to cast ToolStripComboBox to ComboBox. If not then you could use the ToolStripControlHost to place the ComboBox onto your form. - This is incorrect or unnecessary, please see update below the quote.
ToolStripControlHost is the abstract base class for ToolStripComboBox, ToolStripTextBox, and ToolStripProgressBar. ToolStripControlHost can host other controls, including custom controls, in two ways:
Construct a ToolStripControlHost with a class that derives from Control. To fully access the hosted control and properties, you must cast the Control property back to the actual class it represents.
Extend ToolStripControlHost, and in the inherited class's default constructor, call the base class constructor passing a class that derives from Control. This option lets you wrap common control methods and properties for easy access in a ToolStrip.
Use the ToolStripControlHost class to host your customized controls or any other Windows Forms control.
To customize a ToolStripItem, derive from ToolStripControlHost and create a custom implementation. You can override methods such as OnSubscribeControlEvents to handle events raised by the hosted controls, and you can put custom functionality into properties to enhance the hosted control.
Update:
According to the ToolStripComboBox documentation you can access the underlying ComboBox through ToolStripComboBox's ComboBox property.
This is why I usually read the documentation on a control before I use it. I might not understand it, but at least I will have an idea what to look for. :)
You should create Format event like this:
private void comboBox1_Format(object sender, ListControlConvertEventArgs e)
{
e.Value = GetFullIpFormat((string)e.Value);
}
And here is code for formating values:
string GetFullIpFormat(string value)
{
string[] ip = new string[4];
for (int i = 0; i < ip.Length; i++)
{
ip[i] = GetIpPart(i, value);
}
return string.Format("{0:###}.{1:###}.{2:###}.{3:###}", ip[0], ip[1], ip[2], ip[3]);
}
string GetIpPart(int partNumber, string ip)
{
string result = "000";
int iLen = 3;
ip = ip.Replace(".", "");
int iStart = partNumber * iLen;
if (ip.Length > iStart)
{
result = ip.Substring(iStart);
if (result.Length > iLen)
{
result = result.Substring(0, iLen);
}
}
return result;
}
This will do formating for you.
Alternativly you can check input on same event for numbers.
This will do the job for you, happy coding! :)

Silverlight DataGrid SelectedItems.Add is very slow

I'm working on a custom control based on DataGrid, which has its own unique functionality, but also includes some buttons that will come with the control whenever its used. The buttons implement filters, select all, and clear selection. I'm having performance issues with the latter two.
Here is the code that responds to a MVVMLight message indicating that a provided list of objects need to have their selection toggled. In the case of SelectAll and ClearSelection, I use Linq functions to get the appropriate list of points. I know as a fact that the slow part is in the foreach loop. The code above and below the loop changes state of the custom control so that other functions concerned with selection are ignored.
SelectionChanged -= GridSelectionChanged;
_isUpdating = true;
var points = obj.Content;
foreach (var point in points)
{
if (point.IsSelected)
{
SelectedItems.Add(point);
}
if (!point.IsSelected)
{
SelectedItems.Remove(point);
}
}
_isUpdating = false;
SelectionChanged += GridSelectionChanged;
Ctrl+A works wonderfully, which makes this doubly frustrating. Is this all due to SelectedItems.add/remove being slow? Is there some way around this?
Let me know if more information is necessary. Thanks.

System.Windows.Forms.Trackbar - how to prevent user from dragging the slider?

Is there some way to (temporarily) prevent user from changing the value of TrackBar by dragging the slider? Only way I found is to set Enabled property to false but it also greys out the trackbar.
Edit:
Since I´m getting more answers about why shouldn't I do it rather than how it's possible to do it, I decided to explain why I would like to do it.
I'm not using it to allow user adjust value of some application property, but to display and control progress of some action. Imagine for example your favourite media player - it probably contains some control (I'd call it trackbar but English is not my native language so it's maybe wrong)
that displays what part of movie it's currently playing, but also allows you to control it - move back or forward in time and watch the different part.
I use the trackbar in exactly this way - I don't know any other component that would be better (Progressbar won't allow me changing the "position"). It's working fine,
the only thing I'd like to do is not to allow user to "use the trackbar unless the movie is paused".
For this exact reason I've used trackbar component many times in Delphi 6, but when it was disabled it didn't grey out and in my opinion it worked fine. That's why I asked
here if it's possible to achieve the same effect in C#.
The best way would be to set the Enabled property to false, as you rightly pointed out it greys out the track bar too.
Greying out the controls is a windows standard for saying "You cannot use this control at the moment", just imagine you were presented with some controls that was not grayed out where disabled, it would be a very hit and miss affair trying them all to see which is enabled and which isn't.
If you really want to prevent changes without using the Enabled property one possible alternative is to use the ValueChanged event of the TrackBar
private void trackBar1_ValueChanged(object sender, EventArgs e)
{
// Code here to restore the original value
}
If you wanted to be ambitious (or overzealous), you could create a Transparent Panel control to overlay the TrackBar thereby preventing the user from interacting with it, i.e.
public class TransparentPanel : System.Windows.Forms.Panel
{
private const int WS_EX_TRANSPARENT = 0x00000020;
protected override CreateParams CreateParams
{
get
{
CreateParams transparentParams = base.CreateParams;
transparentParams.ExStyle |= WS_EX_TRANSPARENT;
return transparentParams;
}
}
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
// Do Nothing
}
}
Add this control to your form and then position it over the TrackBar, i.e.
transparentPanel.Location = trackBar1.Location;
transparentPanel.Size = trackBar1.Size;
Then you can hide it when you want to allow user interaction.
As Xander pointed out, having a trackbar that looks perfectly normal but can't actually be used is a good way to drive your users crazy!
If you're worried about the trackbar looking "inactive", you might try another way of displaying the data it represents, like a label. That way when you disable the trackbar, you're only indicating that the editing of the value is unavailable.

Categories

Resources