How to paint owner drawn ListViewItem differently when hovered? - c#

Below is the code. I saw that the MouseMove event is not always triggered, especially when the mouse is moved very fast, and because of this two items can be marked hovered at the same time, so I now use a variable to hold the last hovered item, but the problem is that there are too many redraws. I also saw that DrawListViewItemEventArgs.State property is just ShowKeyboardCues when it should also contain Hot.
private void Form1_Load(object sender, EventArgs e)
{
listView1.OwnerDraw = true;
listView1.View = View.LargeIcon;
listView1.DrawItem += ListView1_DrawItem;
listView1.MouseMove += ListView1_MouseMove;
for (int i = 1; i <= 6; ++i)
{
listView1.Items.Add($"item {i}", 0);
}
}
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
ListViewItem item = listView1.GetItemAt(e.X, e.Y);
if (item != null)
{
if (LastHoveredItem != null && LastHoveredItem.Index == item.Index)
{
return;
}
listView1.RedrawItems(item.Index, item.Index, false);
}
}
internal ListViewItem LastHoveredItem = null;
private void ListView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
bool hot = e.Item.Bounds.Contains(listView1.PointToClient(Cursor.Position));
if (LastHoveredItem != null)
{
listView1.RedrawItems(LastHoveredItem.Index, LastHoveredItem.Index, false);
}
if (hot)
{
LastHoveredItem = e.Item;
e.Graphics.FillRectangle(Brushes.Green, e.Bounds);
}
else
{
LastHoveredItem = null;
}
e.DrawText();
}

Here are a few new methods and the existing methods are updated:
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
listView1.OwnerDraw = true;
listView1.View = View.LargeIcon;
listView1.DrawItem += ListView1_DrawItem;
listView1.MouseEnter += ListView1_MouseEnter;
listView1.MouseMove += ListView1_MouseMove;
listView1.MouseLeave += ListView1_MouseLeave;
for (int i = 1; i <= 10; ++i)
{
listView1.Items.Add($"item {i}", 0);
}
}
private void ListView1_MouseEnter(object sender, EventArgs e)
{
CheckHoveredAndInvalidate();
}
private void ListView1_MouseLeave(object sender, EventArgs e)
{
RemoveHoveredAndInvalidate();
}
internal static Rectangle GetEntireItemBounds(ListViewItem it)
{
return it.GetBounds(ItemBoundsPortion.Entire);
}
internal ListViewItem GetEntireItemAtCursorPosition()
{
Point p = listView1.PointToClient(Cursor.Position);
foreach (ListViewItem it in listView1.Items)
{
if (GetEntireItemBounds(it).Contains(p))
{
return it;
}
}
return null;
}
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
CheckHoveredAndInvalidate();
}
private void CheckHoveredAndInvalidate()
{
ListViewItem item = GetEntireItemAtCursorPosition();
if (item == null)
{
RemoveHoveredAndInvalidate();
}
else if (item != null)
{
if (LastHoveredItem != null)
{
if (LastHoveredItem != item)
{
ListViewItem item2 = LastHoveredItem;
LastHoveredItem = item;
listView1.Invalidate(GetEntireItemBounds(item2));
listView1.Invalidate(GetEntireItemBounds(item));
}
else if (LastHoveredItem == item)
{
}
}
else if (LastHoveredItem == null)
{
LastHoveredItem = item;
listView1.Invalidate(GetEntireItemBounds(item));
}
}
}
private void RemoveHoveredAndInvalidate()
{
if (LastHoveredItem != null)
{
ListViewItem item2 = LastHoveredItem;
LastHoveredItem = null;
listView1.Invalidate(GetEntireItemBounds(item2));
}
else if (LastHoveredItem == null)
{
}
}
private void ListView1_DrawItem(object sender, DrawListViewItemEventArgs e)
{
if (LastHoveredItem == e.Item)
{
e.Graphics.FillRectangle(Brushes.Yellow, e.Item.Bounds);
}
else
{
e.Graphics.FillRectangle(Brushes.Green, e.Item.Bounds);
}
}
The text drawing part has been omitted.

Related

A new message box appears with each second after it appears

I ran into an issue where i tried displaying a display box after a countdown reached a certain time but for some odd reason it replicates with each second despite it having already passed the initial time it was supposed to appear. This is what i tried to do but now the timer has stopped and the time remaining column has stopped runng.
public partial class Form1 : Form
{
private List<CSession> sessionlist = new List<CSession>();
private TimeSpan workingTimeSpan = new TimeSpan();
private TimeSpan fiveMinutes = new TimeSpan(0,1,0);
private TimeSpan oneSecond = new TimeSpan(0,0,1);
public Form1()
{
InitializeComponent();
timer1.Enabled = true;
timer1.Start();
}
private void label1_Click(object sender, EventArgs e)
{
}
private void AddTime_Click(object sender, EventArgs e)
{
workingTimeSpan += fiveMinutes;
DisplayWorkingTimeSpan();
}
private void DisplayWorkingTimeSpan()
{
TimerLabel.Text = workingTimeSpan.ToString();
}
private void DecreaseTime_Click(object sender, EventArgs e)
{
TimeSpan fiveMinutes = new TimeSpan(0,5,0);
workingTimeSpan -= fiveMinutes;
DisplayWorkingTimeSpan();
}
private void Confirm_Click(object sender, EventArgs e)
{
CSession newSession = new CSession();
if(PasswordText.Text == "")
{
MessageBox.Show("Password not entered");
return;
}
newSession.password = PasswordText.Text;
newSession.purchased_time = workingTimeSpan;
newSession.remaining_time = workingTimeSpan;
newSession.status = "Online";
sessionlist.Add(newSession);
PasswordText.Text = "";
TimerLabel.Text = "";
workingTimeSpan = new TimeSpan();
}
private void DisplayAllSessions()
{
listView1.Items.Clear();
foreach(CSession c in sessionlist)
{
string[] row = { c.password, c.purchased_time.ToString(), c.remaining_time.ToString(), c.status };
ListViewItem i = new ListViewItem(row);
listView1.Items.Add(i);
}
}
private void timer1_Tick(object sender, EventArgs e)
{
foreach(CSession c in sessionlist)
{
if (c.remaining_time.TotalMinutes == 5)
{
timer1.Stop();
MessageBox.Show("Time almost up for client.");
}
if (c.remaining_time.TotalSeconds < 1)
{
c.status = "Offline";
}
if(c.status == "Online")
{
c.remaining_time -= oneSecond;
}
}
DisplayAllSessions();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
}
Add a flag that gets toggled when you display the message, so you won't display it again:
private bool MessageDisplayed = false;
private void timer1_Tick(object sender, EventArgs e)
{
foreach(CSession c in sessionlist)
{
if (c.remaining_time.TotalMinutes == 5 && !MessageDisplayed) // <-- check the flag
{
MessageDisplayed = true;
MessageBox.Show("Time almost up for client.");
}
if (c.remaining_time.TotalSeconds < 1)
{
c.status = "Offline";
}
if(c.status == "Online")
{
c.remaining_time -= oneSecond;
}
}
DisplayAllSessions();
}
Now you can leave the timer running and your message will only appear once.

ArgumentOutOfRangeException on an object listview when hitting tab

I have an object list view that has two text columns. When I edit the left column and hit tab to go to the right I receive a "ArgumentOutOfRangeException" with an index of -1. Looks like the index is something internal to the list view because I debugged my application and found no errors. Here is the code :
public partial class SummaryOverviewSettingsDlg : Form
{
private List<SummaryDataset> _localSummaryDatasets = new List<SummaryDataset>();
private bool _includeLimits;
private SummaryOverviewSettings _summaryOverviewSettings;
public bool IncludeLimits { get { return _includeLimits; } }
public SummaryOverviewSettingsDlg(SummaryOverviewSettings summaryOverviewSettings)
{
InitializeComponent();
if (summaryOverviewSettings.Datasets != null)
{
_localSummaryDatasets.AddRange(summaryOverviewSettings.Datasets);
}
_summaryOverviewSettings = summaryOverviewSettings;
}
private void DataFilesListDlg_Load(object sender, EventArgs e)
{
foreach(var dataFile in _localSummaryDatasets)
{
olvFilePaths.AddObject(dataFile);
}
LimitsCheckbox.Checked = _summaryOverviewSettings.ShowLimits;
}
private void OlvFilePaths_CellRightClick(object sender, CellRightClickEventArgs e)
{
var contextMenuSymbol = new ContextMenuStrip();
ToolStripItem item;
item = contextMenuSymbol.Items.Add("Add sample");
item.Click += ContextMenuAddFilePath;
if (e.Model != null)
{
contextMenuSymbol.Items.Add("-");
item = contextMenuSymbol.Items.Add("Delete sample");
item.Click += ContextMenuDeleteFilePath;
}
olvFilePaths.ContextMenuStrip = contextMenuSymbol;
}
private void ContextMenuAddFilePath(object sender, EventArgs e)
{
var item = new SummaryDataset()
{
SampleName = "Sample",
Path = "Path"
};
_localSummaryDatasets.Add(item);
// Rebuild the list in the GUI
olvFilePaths.ClearObjects();
foreach (var dataFile in _localSummaryDatasets)
{
olvFilePaths.AddObject(dataFile);
}
olvFilePaths.AutoResizeColumns();
}
private void ContextMenuDeleteFilePath(object sender, EventArgs e)
{
if (olvFilePaths.SelectedObject != null)
{
var item = (SummaryDataset)olvFilePaths.SelectedObject;
olvFilePaths.RemoveObject(item);
_localSummaryDatasets.Remove(item);
}
}
private void OlvFilePaths_CellEditFinished(object sender, CellEditEventArgs e)
{
if (e.Control is TextBox textBox)
{
var oldValue = (string)e.Value;
var newValue = (string)e.NewValue;
var col = e.Column.AspectName;
var index = e.ListViewItem.Index;
if (newValue != oldValue)
{
if (col == "SampleName")
{
_localSummaryDatasets[index].SampleName = newValue;
}
else
{
_localSummaryDatasets[index].Path = newValue;
}
}
}
// Rebuild the list in the GUI
olvFilePaths.ClearObjects();
foreach (var dataFile in _localSummaryDatasets)
{
olvFilePaths.AddObject(dataFile);
}
olvFilePaths.AutoResizeColumns();
}
private void OkButton_Click(object sender, EventArgs e)
{
_summaryOverviewSettings.Datasets.Clear();
_summaryOverviewSettings.Datasets.AddRange(_localSummaryDatasets);
_summaryOverviewSettings.ShowLimits = _includeLimits;
DialogResult = DialogResult.OK;
Close();
}
private void ButtonCancel_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
private void LimitsCheckbox_CheckedChanged(object sender, EventArgs e)
{
_includeLimits = LimitsCheckbox.Checked;
}
}

Custom ToggleButton doesn't respond to ManipulationCompleted event

I have a custom UI element that inherits from System.Windows.Controls.Primitives.ToggleButton. I'm also routing my mouse events through a custom TouchDevice that raises touch events instead.
For some reason, the ManipulationCompleted event never fires. First, the custom TouchDevice can be found here: http://blakenui.codeplex.com/SourceControl/changeset/view/67526#Blake.NUI.WPF/Touch/MouseTouchDevice.cs
Here are the relevant parts of my class:
public class ToggleSwitch: ToggleButton {
private Grid _root;
private readonly IList<int> _activeTouchDevices;
private const double UNCHECKED_TRANSLATION = 0;
private TranslateTransform _backgroundTranslation;
private TranslateTransform _thumbTranslation;
private Grid _root;
private Grid _track;
private FrameworkElement _thumb;
private double _checkedTranslation;
private double _dragTranslation;
private bool _wasDragged;
private bool _isDragging;
public override void OnApplyTemplate()
{
...
MouseTouchDevice.RegisterEvents(_root);
_root.IsManipulationEnabled = true;
_root.TouchDown += OnTouchDown;
_root.TouchUp += OnTouchUp;
_root.GotTouchCapture += OnGotTouchCapture;
_root.LostTouchCapture += OnLostTouchCapture;
_root.ManipulationStarted += OnManipulationStarted;
_root.ManipulationDelta += OnManipulationDelta;
_root.ManipulationCompleted += OnManipulationCompleted;
}
private void OnTouchDown(object sender, TouchEventArgs e)
{
e.TouchDevice.Capture(_root);
}
private void OnGotTouchCapture(object sender, TouchEventArgs e)
{
if (e.TouchDevice.Captured == _root)
{
Manipulation.AddManipulator(_root,e.TouchDevice);
_activeTouchDevices.Add(e.TouchDevice.Id);
}
}
private void OnManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
e.Handled = true;
_isDragging = true;
_dragTranslation = Translation;
ChangeVisualState(true);
Translation = _dragTranslation;
}
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
e.Handled = true;
var horizontalChange = e.DeltaManipulation.Translation.X;
var direction = Math.Abs(horizontalChange) >= Math.Abs(e.DeltaManipulation.Translation.Y) ? Orientation.Horizontal : Orientation.Vertical;
if (direction == Orientation.Horizontal && horizontalChange != 0.0)
{
_wasDragged = true;
_dragTranslation += horizontalChange;
Translation = Math.Max(UNCHECKED_TRANSLATION, Math.Min(_checkedTranslation, _dragTranslation));
}
}
private void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
e.Handled = true;
_isDragging = false;
var click = false;
if (_wasDragged)
{
var edge = (IsChecked ?? false) ? _checkedTranslation : UNCHECKED_TRANSLATION;
if (Translation != edge)
{
click = true;
}
}
else
{
click = true;
}
if (click)
{
OnClick();
}
_wasDragged = false;
}
}
The OnManipulationCompleted method is never entered.

WPF Drag and Drop object from listbox

beginner here.
I'm trying to create a usercontrol with one listbox among other control and I want this listbox to allow drag and drop to other similar instance of the usercontrol.
This is the object I want to drag and drop from one listbox to another :
[Serializable]
public class ListBoxFileName : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string FileNameValue;
public string FileName
{
get { return this.FileNameValue; }
set
{
if (value != this.FileNameValue)
{
this.FileNameValue = value;
NotifyPropertyChanged("FileName");
}
}
}
private bool FileIsSelectedValue;
public bool FileIsSelected
{
get { return this.FileIsSelectedValue; }
set
{
if (value != this.FileIsSelectedValue)
{
this.FileIsSelectedValue = value;
NotifyPropertyChanged("FileIsSelected");
}
}
}
}
Here is how I deal with the drag and drop :
private ListBoxItem _dragged;
private void FileNameList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_dragged != null)
return;
UIElement element = FileNameList.InputHitTest(e.GetPosition(FileNameList)) as UIElement;
while (element != null)
{
if (element is ListBoxItem)
{
_dragged = (ListBoxItem)element;
break;
}
element = VisualTreeHelper.GetParent(element) as UIElement;
}
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (_dragged == null)
return;
if (e.LeftButton == MouseButtonState.Released)
{
_dragged = null;
return;
}
DataObject obj = new DataObject(DataFormats.Serializable, _dragged);
DragDrop.DoDragDrop(_dragged, obj, DragDropEffects.All);
}
private void FileNameList_DragEnter(object sender, DragEventArgs e)
{
if (_dragged == null || e.Data.GetDataPresent(DataFormats.Serializable, true) == false)
e.Effects = DragDropEffects.None;
else
e.Effects = DragDropEffects.All;
}
private void FileListBox_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
{
string[] droppedFilePaths = e.Data.GetData(DataFormats.FileDrop, true) as string[];
for (var i = 0; i < droppedFilePaths.Length; i++)
{
ListBoxFileName filename = new ListBoxFileName();
filename.FileName = droppedFilePaths[i];
filename.FileIsSelected = false;
FileNamesItems.Add(filename);
}
}
if (e.Data.GetDataPresent(DataFormats.Serializable, true))
{
ListBoxFileName BoxItem = new ListBoxFileName();
BoxItem = e.Data.GetData(DataFormats.Serializable) as ListBoxFileName;
}
}
Everything is fine except when the drop event occurs, BoxItem is always null for some reason, so nothing is added to the listbox.
Any hint ?
Thank you
The data of the DataObject should be a ListBoxFileName instead of a ListBoxItem:
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (_dragged == null)
return;
if (e.LeftButton == MouseButtonState.Released)
{
_dragged = null;
return;
}
DataObject obj = new DataObject(DataFormats.Serializable, _dragged.DataContext as ListBoxFileName);
DragDrop.DoDragDrop(_dragged, obj, DragDropEffects.All);
}
private void FileListBox_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Serializable, true))
{
ListBoxFileName BoxItem = e.Data.GetData(DataFormats.Serializable) as ListBoxFileName;
//...
}
}
This should work assuming that the ItemsSource of the "FileNameList" control is set to an IEnumerable.
Please provide all relevant code snippets required to be able to reproduce your issue from scratch if you need any further help on this.

var labels = this.Controls.OfType<Label>(); Control all labels except 1

I have a maze game that uses labels as walls and .IntersectsWith to handle colision. My problem is that since my "player" is also a label it messes things up with my code.
What i want is for the player to be able to move meanwhile it also cant colide with all the other labels.
What the problem really is is that this part makes the player unable to move for some reason. Never mind the if () with break it was just an experiment.
var labels = this.Controls.OfType<Label>();
foreach (var label in labels)
{
if (label.Bounds.IntersectsWith(player.Bounds))
{
break;
}
if (player.Bounds.IntersectsWith(label.Bounds))
{
namespace mazeGame
{
public partial class Form1 : Form
{
bool down;
bool left;
bool right;
bool up;
public Form1()
{
InitializeComponent();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Right)
{
right = true;
left = false;
up = false;
down = false;
}
if (e.KeyCode == Keys.Left)
{
left = true;
right = false;
up = false;
down = false;
}
if (e.KeyCode == Keys.Up)
{
up = true;
left = false;
right = false;
down = false;
}
if (e.KeyCode == Keys.Down)
{
down = true;
left = false;
up = false;
right = false;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
var labels = this.Controls.OfType<Label>();
foreach (var label in labels)
{
if (label.Bounds.IntersectsWith(player.Bounds))
{
break;
}
if (player.Bounds.IntersectsWith(label.Bounds))
{
if (right == true)
{
right = false;
left = true;
}
else if (left == true)
{
left = false;
right = true;
}
else if (up == true)
{
up = false;
down = true;
}
else if (down == true)
{
down = false;
up = true;
}
}
if (right == true)
{
player.Left += 1;
}
if (left == true)
{
player.Left -= 1;
}
if (up == true)
{
player.Top -= 1;
}
if (down == true)
{
player.Top += 1;
}
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void panel2_Paint(object sender, PaintEventArgs e)
{
}
}
}
You can exclude your player label from your selection in this manner:
var labels = this.Controls.OfType<Label>().Where(l => l.Name != "Player")

Categories

Resources