I have a long ListView which contains a list of actions which my app is currently running (each action at a time).
Upon running each action, the relevant ListViewItem is bolded.
The ListView is long so I have vertical scroll bar.
My question is how to make the current running action shown to the user, meaning, how do I "scroll" the list view to that item.
I tried to manually select the row ==> didn't work.
I've used ListViewItem.EnsureVisible but that made the ListView flicker on each change.
Any idea? Thanks.
Edited: I've added some code, even though the code is pretty simple.
Whenever a new action is running, I run the following:
foreach (ListViewItem item in m_ListView.Items)
{
FontStyle style = ((index == value) ? FontStyle.Bold : FontStyle.Regular);
item.Font = new Font(item.Font, style);
if (index == value)
{
m_ModulesListView.EnsureVisible(index);
}
index++;
}
You means
listView1.EnsureVisible(listView1.Items.Count - 1);
Maybe you should call ListView.BeginUpdate() before your loop and ListView.EndUpdate() after your loop.
Found a nice solution.
It involves using win32 but it does the job.
Apparently, it is possible to set double buffering on the list which solves the flickering problem.
So, using EnsureVisible with the double buffering work just fine.
Related
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
Using a worker thread, I am adding items to a ListView, however to do this I have implemented a delegate in the form as shown below in the code sample.
Problem:
When the ListView.Items.add() line is executed, nothing is added to the ListView
What I have tried:
I added a button, when clicked, it successfully adds a ListViewItem to the ListView object, as it should. (this is before the worker thread start, for testing/debugging)
I have also tried swapping the ListView to a RichEdit in case I had a bug in the code, but the items successfully display in the RichEdit, thus I added another ListView incase the first/previous one was buggy, and without renaming the ListView, I tried adding the items, but without success.
What happens in the Delegate:
Nothing, after the clear() method, the 4 columns each with headers disappear and nothing is added to the list view
Conclusion:
My only conclusion is that the delegate is not compatible with the ListView, since I was able to add the items to a RichEdit, but not to a ListView.
Is this a bug? Am I doing something wrong?
code sample
void connected_add_device()
{
string[] t = { "123", "-1", "sa.d.sdf.s.fg", "sda-f-sd-fgds-gf" };
if (display_connected_Devices.InvokeRequired)
display_connected_Devices.BeginInvoke((MethodInvoker)delegate ()
{
display_connected_Devices.Clear();
display_connected_Devices.Items.Add(new ListViewItem(t));
}
);
else
{
display_connected_Devices.Clear();
display_connected_Devices.Items.Add(new ListViewItem(t));
}
}
For what it is worth, I had a bad GIT commit. After everthing is fixed, this ListView (which before the GIT issue, had successfuly displayed the items) now doesn't display the items anymore.
Use display_connected_Devices.Items.Clear(); instead. Because display_connected_Devices.Clear(); will clear, like you noticed, the columns aswell.
I'm using a panorama control in a WP7 app. One of the PanoramaItems takes you to another page, which then allows you send an email through the EmailComposeTask. If you don't select to send the email and press the back button, the Panorama returns to the item you last selected. However, if you do select to send an email (and therefore leave the app), it does not return to the previously selected PanoramaItem. Instead, it returns to the first item in the Panorama. I tried keeping track of the selected index and setting it, but I got an error saying the SelectedIndex is not settable. This is confirmed on MSDN documentation http://msdn.microsoft.com/en-us/library/microsoft.phone.controls.panorama.selectedindex%28VS.92%29.aspx
Is there any way to manually set the selected index/item on a panorama? If not, is there a way for it to remember what was selected, even if the user leaves the app to compose an email?
I'm not sure if you can programmatically force an animation to another PanoramaItem, but you can change the Panorama.DefaultItem.
So you might have 3 PanoramaItem's and on the OnNavigatedTo() handler, change the default item via:
panoramaControl.DefaultItem = panoramaControl.Items[indexToSet];
This should help when you recover from a tombstone.
You could try the solution posted by Silicon Shark in this thread. It's noted to work, but only on the initial display - which shouldn't be a problem for your requirements of restoring state after tombstoning.
How to programmatically set the visible item in a Panorama control?
You can get the currently active page from the panorama's SelectedIndex property.
Unfortunately setting DefualtItem is only an approximation to solving this problem, which you may have discovered already.
Edit: Be aware that setting DefaultItem, changes which page of the panorama is the first page. It's a subtle difference, but you will see how it matters looking at the positioning of the heading and the wrap around of the background image.
Here is a solution. It does work as expected and does not rearrange your panorama, so your user interface is consistent.
pan.SetValue(Panorama.SelectedItemProperty, panoramaItem);
Panorama temp = pan;
LayoutRoot.Children.Remove(pan);
LayoutRoot.Children.Add(temp);
LayoutRoot.UpdateLayout();
this is not a perfect solution in that it does not slide nicely like panorama should, and it is probably not very efficient, but on the other hand you are not changing the default item so your user interface stays consistent.
I tested solutions listed here without success. Here is what I did that works like a charm!
PanoramaItem panItem = (PanoramaItem)panorama.Items[1];
panorama.Items.Remove(panItem);
panorama.Items.Insert(0, panItem);
You need to remove the panel from the list and re-inserting it at the desired position!
Set new selected item by
pan.SetValue(Panorama.SelectedItemProperty, pan.Items[newSelectedItem]);
However, it work only on the initial so my idea is let the panorama control re-init when we change the selected item. This is my code, just add this after Panorama.SelectedItem changing.
(pan.Items[curIndex] as PanoramaItem).Visibility = Visibility.Collapsed;
pan.SetValue(Panorama.SelectedItemProperty, pan.Items[(curIndex + 1) % pan.Items.Count]);
pan.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
(pan.Items[curIndex] as PanoramaItem).Visibility = Visibility.Visible;
But there is not transition effect now! Although, you can create your self.
It work great for me, this page also create a effect for sliding right http://xme.im/slide-or-change-panorama-selected-item-programatically
I'm using this model to change to a pivot when the device goes into landscape view, I'll probably end up extracting the current item to the application state. The panorama is a no-go in landscape orientation.
private int hub_page_index;
protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
base.OnOrientationChanged(e);
if (panorama.Visibility == Visibility.Visible)
{
hub_page_index = panorama.SelectedIndex;
}
else if (pivot.Visibility == Visibility.Visible)
{
hub_page_index = pivot.SelectedIndex;
}
if (e.Orientation == PageOrientation.Landscape
|| e.Orientation == PageOrientation.LandscapeLeft
|| e.Orientation == PageOrientation.LandscapeRight)
{
// Display Pivot in Landscape orientation
pivot.SetValue(Pivot.SelectedItemProperty, pivot.Items[panorama.SelectedIndex]);
panorama.Visibility = Visibility.Collapsed;
pivot.Visibility = Visibility.Visible;
}
else
{
// Display Panorama in Portrait orientation
panorama.SetValue(Panorama.SelectedItemProperty, panorama.Items[pivot.SelectedIndex]);
pivot.Visibility = Visibility.Collapsed;
panorama.Visibility = Visibility.Visible;
}
}
I have an Application that runs tests on a customers account in order to judge if their service is working correctly. During the process of running the tests, the application reads each test and checks to see if it passes / fails / etc... It plays a green checkmark / red x on the tabPage itself as an imagekey... the imagekey is assigned as so
(tabPage as TabPage).ImageKey = "pass.png";
tabPage is actually an object that is passed through to the function so i can refer to it from a different method.
When the tabControl for the tabPage is created (dynamically), an imageList is added to the tabControl (which is where the images are pulled from).
(tabControl[0] as TabControl).ImageList = imageList2;
So when the method finally gets around to the code to assign the ImageKey it does run through the code, however it just shows up as a blank image. It's weird, because it works for some people and not others. It does not currently work on mine atm either and they do not show when I execute the source code. Does anyone have any ideas? Here's an image to help describe the issue... More code to follow if needed.
Your need for an answer has probably long-since passed, but I had the same problem, and here is my solution:
Setting the ImageKey does not work, but setting the ImageIndex does cause the icon to display. I made an enumerator that mapped the icons to their indices.
private enum TabIconCodes : int
{
Stopped = 1,
Running = 2,
}
unitTab.ImageIndex = (int)TabIconCodes.Stopped;
This is not an ideal solution, but it does work.
Hy! Another solution on the same concept:
tabp.ImageIndex = ImageListTabpages.Images.IndexOfKey("ImageKey")
It does work for me.
I understand this question was asked a long time ago but the answer provided here either is a workaround or not clear enough.
This does NOT show the image:
var tabPage = new TabPage("page text");
tabPage.Name = "pageName";
tabPage.ImageKey = "myImageKey";
TabPages.Add(tabPage); //tab is added AFTER setting the ImageKey
This works fine:
var tabPage = new TabPage("page text");
TabPages.Add(tabPage); //tab is added BEFORE setting the ImageKey
tabPage.Name = "pageName";
tabPage.ImageKey = "myImageKey";
Hope this helps!
Before setting up the ImageKey for PageTab the TabControl of the PageTab needs to have ImageList set and PageTab has to be added to the TabControl
I have a small problem that has been annoying me for some hours.
In my WinForms (.NET 3.5) application I create some ComboBoxes (DropDownStyle = DropDown) in a TableLayoutPanel at runtime and fill it with strings. The ComboBoxes are configured to resize automatically (Anchor = Left | Right).
The problem is that whenever the ComboBoxes are resized (i.e. the dialog is resized), the editbox portion of the ComboBox gets selected/highlighted entirely. In my opinion this creates a very confusing effect for the customer which I want to avoid.
The problem doesn't appear if the ComboBox has a fixed size.
Also note that changing the DropDownStyle is not an option - I need the possibility to enter text manually.
I already tried messing around with overriding the OnPaint method, which didn't quite work.
I also tried clearing the selection in the ComboBox.Resize event, which worked in a way, but seemed like a very ugly solution - there was a lot of flicker, intentionally selected text became deselected and I would have to add the event handler to each and every ComboBox on my dialog.
Is there a better solution to this problem?
Thank you in advance.
Regards,
Andy
This is an old question, but I found it searching for an answer and ended up implementing my own solution. Might as well post it here, right?
foreach (var cb in Controls.OfType<ComboBox>())
{
cb.Resize += (sender, e) => {
if (!cb.Focused)
cb.SelectionLength = 0;
};
}
intentionally selected text became deselected
This WinForms bug doesn't affect selected ComboBoxes, so by ignoring the ones with Focus, we take care of the problem of losing current selections.
I would have to add the event handler
to each and every ComboBox on my
dialog.
Taken care of by the foreach loop. Put it in InitializeComponent() or your .ctor if you don't want to break the designer, or have the designer break this.
there was a lot of flicker
I'm only seeing flicker if I resize very fast, but I'm on Win7 so it might be different on XP.
This appears to be a bug in the native Windows implementation of ComboBox with DropDownStyle of DropDown.
The fix detailed in the other answers here (setting the SelectionLength property to 0 (zero) in the ComboBox's Resize event) works well most of the time.
However, I found that even that fix to work around this bug does not always work. If the ComboBox is in a TableLayoutPanel, and if that TableLayoutPanel has more than one column with a Percent Size Type, then that fix often does not work.
A picture is worth a thousand words. See the following screen shot of a form I made to demonstrate the problem.
Worked for me to change the selectionLength to 0 when the WM_WINDOWPOSCHANGED gets called.
Works even with the tableLayoutPanel set to %.
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if(m.Msg == 0x0047) { // WM_WINDOWPOSCHANGED = 0x0047
if (SelectionLength != 0) {
SelectionLength = 0;
}
}
}
Wow. Thank you guys!
Apparently this bug has persisted many years.
I'm building a UserControl with .Net 4 (Visual Studio 2010).
Here's is my slightlty modified version of bsneeze's code.
Cheers
using System.Windows.Forms;
using System.Linq;
public MyUserControlCtor()
{
InitializeComponent();
foreach( Control ctrl in Controls)
{
ComboBox cb = ctrl as ComboBox;
if (cb != null)
{
cb.Resize += (sender, e) =>
{
if (!cb.Focused)
this.FCHZ_ComboBox.SelectionLength = 0;
};
}
}
}
None of the answers so far worked for me. The only reliable method I have found was to post a message asynchronously via BeginInvoke that sets SelectionLength back to zero after all other activity on the control has completed. The amount of flicker is very annoying and unprofessional, but it is the best I could come up with...so far.
internal class FixedComboBox : ComboBox
{
protected override void OnResize(EventArgs e)
{
if (IsHandleCreated && !Focused)
{
BeginInvoke((Action)(() =>
{
SelectionLength = 0;
}));
}
base.OnResize(e);
}
}
I found setting the selection length to 0 for the combo-box on the resize event of whatever control the combo-box is on causes a lot less flickering instead of doing it on the resize of the combo itself.
I actually achieved this in VB.Net but it should apply the same to C#.
Handle the Resize event for the ComboBox's parent container. Put this line in there:
MyComboBox.SelectionLength = 0
An Example (VB, obviously):
Private Sub MyControl_Resize(sender As Object, e As EventArgs) Handles Me.Resize
MyComboBox.SelectionLength = 0
End Sub
Good Luck to you!
--BP
For a ComboBox inside a TableLayoutPanel setting the .SelectionLength = 0 on the ComboBox.Resize event does not work, but doing this on the TableLayoutPanel.Resize event does:
Private Sub TableLayoutPanel_Resize(sender As Object, e As EventArgs)
Dim curr_panel = TryCast(sender, System.Windows.Forms.TableLayoutPanel)
For Each curr_combo As ComboBox In curr_panel.Controls.OfType(Of ComboBox)
If ((Not curr_combo.Focused) And curr_combo.DropDownStyle = ComboBoxStyle.DropDown) Then
curr_combo.SelectionLength = 0
End If
Next
End Sub
dim panel as new TableLayoutPanel with {
...
}
AddHandler panel.Resize, AddressOf TableLayoutPanel_Resize