Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
This post was edited and submitted for review 11 months ago and failed to reopen the post:
Needs details or clarity Add details and clarify the problem by editing this post.
Improve this question
I want to create a dotted lines that is used as input for typing text on it instead using a text box.
For example:
Instead of text box:
I want to make something like this:
and to be able to type on it.
In the Paint event of a panel that is hosting a borderless TextBox control, you can draw it like this:
private void Panel1_Paint(object sender, PaintEventArgs e) {
using (Pen p = new Pen(Color.Black)) {
p.DashStyle = DashStyle.Dash;
e.Graphics.DrawLine(p, new Point(textBox1.Left, textBox1.Bottom + 2),
new Point(textBox1.Right, textBox1.Bottom + 2));
}
}
Ends up looking like this:
You can try with a Custom Control derived from TextBox.
Overriding its WndProc and handling WM_PAINT, you can draw a dotted/dashed line (or anything else) inside the edit control's ClientArea. Then refresh the line drawing when keys are pressed, so the line is not erased.
WM_KEYDOWN, WM_LBUTTONDOWN and WM_CAPTURECHANGED are also handled, since these can (will) cause the painting of the background.
The position of the base line is calculated using the FontFamily's GetLineSpacing() and GetCellAscent() methods, then moved down the size of the Pen used to draw the line.
OnFontChanged() is overridden to get a notification when the Font changes.
Some more info here: Properly draw text using Graphics Path
It's a little raw, but see how it goes :)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
[ToolboxItem(true)]
[DesignerCategory("Code")]
public class TextBoxBaseline : TextBox
{
private const int WM_PAINT = 0x000F;
private const int WM_KEYDOWN = 0x0100;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_CAPTURECHANGED = 0x0215;
private float m_lnSpacing = 0.0f;
private float m_cellAscent = 0.0f;
public TextBoxBaseline() {
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.BorderStyle = BorderStyle.None;
}
protected override void OnFontChanged(EventArgs e) {
base.OnFontChanged(e);
m_lnSpacing = Font.FontFamily.GetLineSpacing(Font.Style);
m_cellAscent = Font.FontFamily.GetCellAscent(Font.Style);
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
switch (m.Msg) {
case WM_PAINT:
using (var g = Graphics.FromHwnd(this.Handle))
using (Pen pen = new Pen(Color.DimGray, 1)) {
pen.DashStyle = DashStyle.Dash;
int baseLine = (int)(Font.GetHeight(g) * m_cellAscent / m_lnSpacing + pen.Width);
g.DrawLine(pen, 1, baseLine, ClientSize.Width - 1, baseLine);
m.Result = IntPtr.Zero;
}
break;
case WM_KEYDOWN:
case WM_LBUTTONDOWN:
case WM_CAPTURECHANGED:
this.Invalidate();
break;
}
}
}
It looks like this:
If you don't know how to used this code:
Add a new class to your Project and name it TextBoxBaseline.
Copy the using directives you find here and paste them on top of the class.
Copy the rest of the code and replace the default class definition in your class file, without removing the namespace.
Build the Project.
You'll find the TextBoxBaseline Control in the ToolBox. Open up a Form and drop it on it as usual.
Related
I have an application and i want to make its background color as default Windows 10 Accent color and when user changes Windows 10's Accent color i want my application's background color also changes.
You would need to make a call to UISettings.GetColorValue(UIColorType.Background) as documented here.
WinForms is an old technology that has not been updated much to support new OS features. There is no good way to get the Accent color using the base .Net libraries (though there is an registry based hack using undocumented keys to retrieve the value). Fortunately, you can access some of the Windows Runtime APIs by adding the NUGET package Microsoft.Windows.SDK.Contracts.
With this package added, you can create an instance of the Windows.UI.ViewManagement.UISettings Class and then use the UISettings.GetColorValue(UIColorType) Method to retrieve the value.
To be notified of changes to the value, you can subscribe to the SystemEvents.UserPreferenceChanged Event. This event categorizes the change as a UserPreferenceCategory.General type change that is the default when the old logic does not know what has changed (again old code not updated for new features). You could detect changes by listenimg for the WM_SETTINGCHANGE message and check for when the WParam is null and the LParam points to a string ("ImmersiveColorSet"), but this replies upon the string value never changing and is not much better than reacting to all UserPreferenceCategory.General changes.
With all that stated, a simple implementation would be as follows:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
UserPreferenceChangedEventHandler UserPerferenceChanged = (s, e) =>
{ if (e.Category == UserPreferenceCategory.General || e.Category == UserPreferenceCategory.VisualStyle) BackColor = GetAccentColor(); };
SystemEvents.UserPreferenceChanged += UserPerferenceChanged;
Disposed += (object s, EventArgs e) => { SystemEvents.UserPreferenceChanged -= UserPerferenceChanged; };
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
BackColor = GetAccentColor();
}
static Windows.UI.ViewManagement.UISettings uiSettings = new Windows.UI.ViewManagement.UISettings();
private static System.Drawing.Color GetAccentColor()
{
Windows.UI.Color c = uiSettings.GetColorValue(Windows.UI.ViewManagement.UIColorType.Accent);
return Color.FromArgb(c.A, c.R, c.G, c.B);
}
}
You can override WndProc to handle the WM_SETTINGCHANGE message.
When this message is received, you can determine whether the settings change is related to a Desktop change inspecting the Message WParam: if it's SPI_SETDESKWALLPAPER, then Desktop settings have changed.
A change in the Background Color is notified like this.
When you get the message, the Color value has already been changed so you can retrive it using the SystemColors class: SystemColors.Desktop returns the current Color of the Desktop.
private const int WM_SETTINGCHANGE = 0x001A;
private const int SPI_SETDESKWALLPAPER = 0x0014;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg) {
case WM_SETTINGCHANGE:
if (m.WParam.ToInt32() == SPI_SETDESKWALLPAPER) {
this.BackColor = SystemColors.Desktop;
}
m.Result = IntPtr.Zero;
break;
// other case switches
}
Update 1: I've written both a MFC-C++ implementation and an old-school Win32 app and recorded a video demonstrating how bad the issue really is:
https://www.youtube.com/watch?v=f0CQhQ3GgAM
Since the old-school Win32 app is not exhibiting this issue, this leads me to believe that C# and MFC both use the same rendering API that must cause this issue (basically discharging my suspicion that the problem might be at the OS / graphics driver level).
Original post:
While having to display some REST data inside a ListView, I encountered a very peculiar problem:
For certain inputs, the ListView rendering would literally slow to a crawl while scrolling horizontally.
On my system and with the typical subclassed ListView with "OptimizedDoubleBuffer", having a mere 6 items in a ListView will slow down rendering during scrolling to the point that i can see the headers "swimming", i.e. the rendering of the items and headers during the scrolling mismatches.
For a regular non-subclassed ListView with 10 items, I can literally see each item being drawn separately while scrolling (the repainting takes around 1-2s).
Here's example code (and yes, I am aware that these look like bear and butterfly emotes; this issue was found from user-provided data, after all):
using System;
using System.Windows.Forms;
namespace SlowLVRendering
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
const string slow = "ヽ( ´。㉨°)ノ Ƹ̴Ӂ̴Ʒ~ ღ ( ヽ( ´。㉨°)ノ ༼ つ´º㉨º ༽つ ) (」゚ペ)」ヽ( ´。㉨°)ノ Ƹ̴Ӂ̴Ʒ~ ღ ( ヽ( ´。㉨°)ノ ༼ つ´º㉨º ༽つ ) (」゚ペ)」";
ListView lv = new ListView();
lv.Dock = DockStyle.Fill;
lv.View= View.Details;
for (int i = 0; i < 2; i++) lv.Columns.Add("Title "+i, 500);
for (int i = 0; i < 10; i++)
{
var lvi = lv.Items.Add(slow);
lvi.SubItems.Add(slow);
}
Controls.Add(lv);
}
}
}
Can someone explain what the issue is, and how to resolve it?
After trying a few different things, here is the fastest solution I found. There is still a little hesitation, but not anywhere near as your original solution. Until Microsoft decides to use something better than GDI+, it doesn't get better unless you go to WPF with .NET 4 and above. Oh well.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SlowLVRendering
{
[System.Runtime.InteropServices.DllImport("user32")]
private static extern bool SendMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);
private uint LVM_SETTEXTBKCOLOR = 0x1026;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
const string slow = "ヽ( ´。㉨°)ノ Ƹ̴Ӂ̴Ʒ~ ღ ( ヽ( ´。㉨°)ノ ༼ つ´º㉨º ༽つ ) (」゚ペ)」ヽ( ´。㉨°)ノ Ƹ̴Ӂ̴Ʒ~ ღ ( ヽ( ´。㉨°)ノ ༼ つ´º㉨º ༽つ ) (」゚ペ)」";
ListView lv = new BufferedListView();
// new ListView();
//new ListViewWithLessSuck();
lv.Dock = DockStyle.Fill;
lv.View = View.Details;
for (int i = 0; i < 2; i++) lv.Columns.Add("Title " + i, 500);
for (int i = 0; i < 10; i++)
{
var lvi = lv.Items.Add(slow);
lvi.SubItems.Add(slow);
}
Controls.Add(lv);
//SendMessage(lv.Handle, LVM_SETTEXTBKCOLOR, IntPtr.Zero, unchecked((IntPtr)(int)0xFFFFFF));
}
}
public class BufferedListView : ListView
{
public BufferedListView()
: base()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
}
class ListViewWithLessSuck : ListView
{
[StructLayout(LayoutKind.Sequential)]
private struct NMHDR
{
public IntPtr hwndFrom;
public uint idFrom;
public uint code;
}
private const uint NM_CUSTOMDRAW = unchecked((uint)-12);
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x204E)
{
NMHDR hdr = (NMHDR)m.GetLParam(typeof(NMHDR));
if (hdr.code == NM_CUSTOMDRAW)
{
m.Result = (IntPtr)0;
return;
}
}
base.WndProc(ref m);
}
}
You can see for yourself the difference. If you uncomment the SendMessage and change new BufferedListView(); to new ListViewWithLessSuck(); You can see the change.
I believe I have narrowed the problem down to Visual Styles. Commenting out Application.EnableVisualStyles(); in static void Main results in a huge performance boost during scrolling, though nowhere near the performance of the Win32 app as shown in the video that I mentioned in Update 1.
The downside of this of course is that all controls in your application will look "old". I've therefore experimented with selectively disabling / enabling of visual styles through
[DllImport("uxtheme", ExactSpelling = true, CharSet = CharSet.Unicode)]
public extern static Int32 SetWindowTheme(IntPtr hWnd, String textSubAppName, String textSubIdList);
and using Win32.SetWindowTheme(lv.Handle, " ", " "); as described in the MSDN docs. The most logical thing would be to keep Visual Styles active for most of the controls and turn if off for performance critical ones. However, a part of the ListView seems to deliberately ignore whether visual styles are disabled or enabled, namely the column headers of the listview in report mode:
(Note how the column header looks in comparison to the scroll bars)
So unless someone knows how to force visual styles off on listview column headers, this is a "pick your poison" kind of situation: Either comment out Application.EnableVisualStyles(); and have an ugly looking UI or leave it in and risk unpredictable renderer slowdowns.
If you go for the first choice, you can get another huge performance boost by subclassing the ListView and short-circuiting the WM_REFLECT_NOTIFY message (thanks to SteveFerg for the original):
public class ListViewWithoutReflectNotify : ListView
{
[StructLayout(LayoutKind.Sequential)]
private struct NMHDR
{
public IntPtr hwndFrom;
public uint idFrom;
public uint code;
}
private const uint NM_CUSTOMDRAW = unchecked((uint) -12);
public ListViewWithoutReflectNotify()
{
}
protected override void WndProc(ref Message m)
{
// WM_REFLECT_NOTIFY
if (m.Msg == 0x204E)
{
m.Result = (IntPtr)0;
return;
//the if below never was true on my system so i 'shorted' it
//delete the 2 lines above if you want to test this yourself
NMHDR hdr = (NMHDR) m.GetLParam(typeof (NMHDR));
if (hdr.code == NM_CUSTOMDRAW)
{
Debug.WriteLine("Hit");
m.Result = (IntPtr) 0;
return;
}
}
base.WndProc(ref m);
}
}
Disabling visual styles and subclassing allow for rendering speeds nearly on par of that of the Win32 C app. However, I do not fully understand the potential ramifications of shorting WM_REFLECT_NOTIFY, so use with care.
I've also checked the Win32 app and confirmed that you can literally kill the rendering performance of your app simply by adding a manifest, for example like so:
// enable Visual Styles
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
I'm very new to C#.
Below is a code that I'm trying to create forms and containers within the code; but I've problems with it.
I start with a new Windows Forms Application template.
I change the Program.cs file a little, so that I'd be able to create the FormMain dynamically.
When the lines Container.Add(BtnClose) and BtnClose_Setup() in FormMain.cs are commented, the code compile and run. However, there are still some weird results in the program.
(a) The form FormMain is supposed to show up at (20, 20) (upper left corner), as the FormMain_Setup says; but when I run the app, though width & height settings show up as expected (800, 600), the upper left corner changes every time (does not stick to 20, 20).
(b) The esc key works as expected and closes the form and application.
When the lines Container.Add(BtnClose) and BtnClose_Setup() in FormMain.cs are not commented, the code compile but VS sends me a message when it's run: "An unhandled exception of type 'System.TypeInitializationException' occurred in mscorlib.dll"
Can someone tell me what I'm doing wrong?
Program.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace test {
static class Program {
public static FormMain FormMain = new FormMain();
[STAThread]
static void Main() {
Application.Run(FormMain);
}
}
}
FormMain.cs file:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace test {
public partial class FormMain : Form {
Button BtnClose = new Button();
public void BtnClose_Setup() {
BtnClose.Text = "Ok";
BtnClose.Top = 500;
BtnClose.Left = 700;
}
public void FormMain_Setup() {
Top = 20;
Left = 20;
Width = 800;
Height = 600;
KeyDown += FormMain_KeyDown;
//Container.Add(BtnClose);
//BtnClose_Setup();
}
void FormMain_KeyDown(object sender, KeyEventArgs e) {
if(e.KeyCode == Keys.Escape) {
Close();
}
}
public FormMain() {
InitializeComponent();
FormMain_Setup();
}
}
}
Call Controls.Add(BtnClose); instead of Container.Add(BtnClose);.
As for fixing the form position: set StartPosition = FormStartPosition.Manual; property.
To properly close the form on Esc, override ProcessCmdKey method:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape)
{
Close();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
By default a forms StartPosition is set to WindowsDefaultLocation. You need to set it to Manual; either in the designer or in the code.
To add a control to a form, you want to add it to the form's Controls collection, not the Container.
Also, if you want the form to continue to get KeyDown events after the button is added you need to set KeyPreview to true.
public void FormMain_Setup()
{
StartPosition = FormStartPosition.Manual;
KeyPreview = true;
Top = 20;
Left = 20;
Width = 800;
Height = 600;
KeyDown += FormMain_KeyDown;
Controls.Add(BtnClose);
BtnClose_Setup();
}
I managed to get a keyboard in windows 8 to show when clicking on a NumericUpDown box in a new form that I made pop up. Unfortunately, it seems that after closing the keyboard on "lost focus", the window is distorted and wont show the entire program until that popup window is closed.
//Close keyboard
void CopiedNudPass_LostFocus(object sender, EventArgs e)
{
Version win8version = new Version(6, 2, 9200, 0);
if (Environment.OSVersion.Version >= win8version)
{
Process[] oskProcessArray = Process.GetProcessesByName("TabTip");
foreach (Process onscreenProcess in oskProcessArray)
{
onscreenProcess.Kill();
}
Refresh();
}
}
So, basically, I need to refresh the background window when closing the keyboard from the currently opened form. Any advice is appreciated. Thank you.
I found my solution here:
After killing the process for TabletKeyboard(TabTip.exe) application doesn't bring back to its original size in wpf
Here is my new close code:
//Close keyboard
void CopiedNudPass_LostFocus(object sender, EventArgs e)
{
Version win8version = new Version(6, 2, 9200, 0);
if (Environment.OSVersion.Version >= win8version)
{
uint WM_SYSCOMMAND = 274;
uint SC_CLOSE = 61536;
IntPtr KeyboardWnd = FindWindow("IPTip_Main_Window", null);
PostMessage(KeyboardWnd.ToInt32(), WM_SYSCOMMAND, (int)SC_CLOSE, 0);
}
}
I also had to add a reference to WindowsBase and add external functions to the project. The steps and additional code are in the url I linked to in this post. Here's how you add a reference for WindowsBase to get using System.Windows.Interop; to work:
Right click on project
Highlight Add and click Reference
Ensure you have Framework selected under Assemblies
Scroll down and check in "WindowsBase" and hit ok
Add using System.Windows.Interop; at the top of your code and your done
Well, this is probably the dumbest question yet, but I have a huge problem that bothers me. First off, I used the code sample from Mick Doherty's Tab Control Tips under reposition TabItems at runtime to allow drag and drop of my tabs in my control. The problem is I use a custom TabPage class by the name of ExtendedTabPage and this causes me trouble. I tried casting or using the as keyword but I am out of luck, so I would like someone to help me out on how to refactor the code to allow for dragging of the custom tabs.
EDIT: I forgot to mention that ExtendedTabPage is an abstract class that is inherited by my objects (for example one of my Windows belongs to ConsoleTab class that inherits ExtendedTabPage). Is this any relevant to the problem itself?
EDIT 2: Major discovery - In the DragOver method, if I try using ConsoleTab in the typeof statements, it seems to work perfectly fine. The problem is that I do not want to do this specifically for this class but for all classes inheriting from its parent class, which is abstract (and I can actually convert it to non-abstract if need be but I won't actively use it...).
EDIT 3: Maybe a good way would be to directly swap using the indexes and avoid using the TabPage Data, however I am a bit confused on how to do this...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ReCodeConsole.Properties;
namespace ReCodeConsole.Core
{
/// <summary>
/// Implements all the extra functionality needed for tab pages on top of the existing TabControl.
/// Includes events for DrawItem, MouseMove and MouseDown.
/// </summary>
public partial class ExtendedTabControl : TabControl
{
/// <summary>
/// Initializes a new instance of the ExtendedTabControl class. All events are added.
/// </summary>
public ExtendedTabControl() :base()
{
this.DrawItem+=new DrawItemEventHandler(DrawTab);
this.MouseClick+=new MouseEventHandler(Tab_OnMouseDown);
this.MouseMove+=new MouseEventHandler(Tab_OnMouseMove);
this.DragOver+=new DragEventHandler(Tab_OnDragOver);
}
/// <summary>
/// Used to store the starting position of a tab drag event.
/// </summary>
private Point DragStartPosition = Point.Empty;
private void DrawTab(object sender, DrawItemEventArgs e)
{
//
//This code will render the close button at the end of the Tab caption.
//
e.Graphics.DrawImage(Resources.TabCloseButton, e.Bounds.Right - 22, e.Bounds.Top + 5, 14, 14);
e.Graphics.DrawString(this.TabPages[e.Index].Text, e.Font, Brushes.Black, e.Bounds.Left + 12, e.Bounds.Top + 3);
e.DrawFocusRectangle();
}
private void Tab_OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
//
// Regardless of where the MouseDown event originated, save the coordinates for dragging.
//
DragStartPosition = new Point(e.X, e.Y);
#region Close Button Handling
//
// Close button code - looping through the controls.
//
for (int i = 0; i < this.TabPages.Count; i++)
{
Rectangle r = GetTabRect(i);
//
//Getting the position of the close button.
//
Rectangle closeButton = new Rectangle(r.Right - 22, r.Top + 5, 14, 14);
if (closeButton.Contains(e.Location))
{
if (this.TabPages[i] is ExtendedTabPage)
{
if ((this.TabPages[i] as ExtendedTabPage).IsCloseable)
{
if (MessageBox.Show("Are you sure you want to close this tab?", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.TabPages.RemoveAt(i);
break;
}
}
}
else
{
if (MessageBox.Show("Are you sure you want to close this tab?", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
this.TabPages.RemoveAt(i);
break;
}
}
}
}
#endregion
}
private TabPage HoverTab()
{
for (int index = 0; index <= TabCount - 1; index++)
{
if (GetTabRect(index).Contains(PointToClient(Cursor.Position)))
return (TabPage)TabPages[index];
}
return null;
}
private void Tab_OnDragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
TabPage hover_Tab = HoverTab();
if (hover_Tab == null)
e.Effect = DragDropEffects.None;
else
{
if (e.Data.GetDataPresent(typeof(TabPage)))
{
e.Effect = DragDropEffects.Move;
TabPage drag_tab = (TabPage)e.Data.GetData(typeof(TabPage));
if (hover_Tab == drag_tab) return;
Rectangle TabRect = GetTabRect(TabPages.IndexOf(hover_Tab));
TabRect.Inflate(-3, -3);
if (TabRect.Contains(PointToClient(new Point(e.X, e.Y))))
{
SwapTabPages(drag_tab, hover_Tab);
SelectedTab = drag_tab;
}
}
}
}
private void Tab_OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
Rectangle r = new Rectangle(DragStartPosition, Size.Empty);
r.Inflate(SystemInformation.DragSize);
TabPage tp = HoverTab();
if (tp != null)
{
if (!r.Contains(e.X, e.Y))
DoDragDrop(tp, DragDropEffects.All);
}
DragStartPosition = Point.Empty;
}
private void SwapTabPages(TabPage tp1, TabPage tp2)
{
int Index1 = this.TabPages.IndexOf(tp1);
int Index2 = this.TabPages.IndexOf(tp2);
this.TabPages[Index1] = tp2;
this.TabPages[Index2] = tp1;
}
}
}
Now ignore anything that is extra, I give you the whole code in case anything else messes it all up. So to recap, I want someone to fix the code or at least explain how to do so (or if it isn't possible what to do) in order for me to be able to swap between ExtendedTabPage items instead of regular TabPage. I have tried and regular TabPage objects work, whereas my custom ones don't. If the solution has to include only one of them (so normal TabPages cannot work), go for only ExtendedTabPage as I can convert the rest of the stuff into that. I hope this is not like really dumb and very much a situation where I am overlooking something.
P.S: I also checked the page I linked for a solution that worked for custom classes, but with no luck, as it caused me twice the problems and half my code broke even with the proper assembly references, so that is not a real option. :/
Well, as nobody seems to know the solution and after excessive testing and tweaking plus some very lucky finds in the site, namely How to deal with GetDataPresent to let it accept all the derived types and C# Drag and Drop - e.Data.GetData using a base class, I am happy to report that the problem can easily enough be solved by substituting the GetPresentData and GetData calls. I provide the tweaked code for Tab_OnDragOver which was where the problem originated and I hope it all works fine for anyone who hits this page and looks for a solution!
private void Tab_OnDragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
TabPage hover_Tab = HoverTab();
if (hover_Tab == null)
e.Effect = DragDropEffects.None;
else
{
var drag_tab = e.Data.GetData(e.Data.GetFormats()[0]);
if (typeof(TabPage).IsAssignableFrom(drag_tab.GetType()))
{
e.Effect = DragDropEffects.Move;
if (hover_Tab == drag_tab) return;
Rectangle TabRect = GetTabRect(TabPages.IndexOf(hover_Tab));
TabRect.Inflate(-3, -3);
if (TabRect.Contains(PointToClient(new Point(e.X, e.Y))))
{
SwapTabPages(drag_tab as TabPage, hover_Tab);
SelectedTab = drag_tab as TabPage;
}
}
}
}