.Net-Komponente die einen Drehregler-Konpf darstellt
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

KnobControl.cs 44KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. #region License
  2. /* Copyright (c) 2017 Fabrice Lacharme
  3. * This code was originally written by Jigar Desai
  4. * http://www.c-sharpcorner.com/article/knob-control-using-windows-forms-and-gdi/
  5. * Note that another implementation exists in vb.net by Blong
  6. * https://www.codeproject.com/Articles/2563/VB-NET-Knob-Control-using-Windows-Forms-and-GDI?msg=1884770#xx1884770xx
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to
  10. * deal in the Software without restriction, including without limitation the
  11. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  12. * sell copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. */
  26. #endregion
  27. #region Contact
  28. /*
  29. * Fabrice Lacharme
  30. * Email: fabrice.lacharme@gmail.com
  31. */
  32. #endregion
  33. using System;
  34. using System.ComponentModel;
  35. using System.Drawing;
  36. using System.Drawing.Drawing2D;
  37. using System.Windows.Forms;
  38. namespace KnobControl {
  39. /* Original code from Jigar Desai on C-SharpCorner.com
  40. * see https://www.c-sharpcorner.com/article/knob-control-using-windows-forms-and-gdi/
  41. * KnobControl is a knob control written in C#
  42. *
  43. * CodeProject: https://www.codeproject.com/Tips/1187460/Csharp-Knob-Control-using-Windows-Forms
  44. * Github: https://github.com/fabricelacharme/KnobControl
  45. *
  46. * 22/08/18 - version 1.0.O.1
  47. * Fixed: erroneous display in case of minimum value <> 0 (negative or positive)
  48. * Modified: DrawColorSlider, OnMouseMove
  49. *
  50. * Added: Font selection
  51. *
  52. *
  53. * 25/08/18 - version 1.0.0.2
  54. * Fixed: mouse click event: pointer button is not displayed correctly when the minimum is set to a non zero value.
  55. * Modified: getValueFromPosition
  56. *
  57. *
  58. * 04/01/2019 - version 1.0.0.3
  59. * Font & Size selection for graduations:
  60. * New property ScaleFontAutoSize:
  61. * - false = no AutoSize => Allow font selection
  62. * - true = AutoSize by program
  63. */
  64. // A delegate type for hooking up ValueChanged notifications.
  65. public delegate void ValueChangedEventHandler(object Sender);
  66. // A delegate type for hooking up mouse clicks on middle area notifications.
  67. public delegate void MiddleAreaMouseClickEventHandler(object Sender);
  68. /// <summary>
  69. /// Summary description for KnobControl.
  70. /// </summary>
  71. public class KnobControl : System.Windows.Forms.UserControl {
  72. /// <summary>
  73. /// Required designer variable.
  74. /// </summary>
  75. private System.ComponentModel.Container components = null;
  76. /// <summary>
  77. /// Styles of pointer button
  78. /// </summary>
  79. public enum KnobPointerStyles {
  80. circle,
  81. line,
  82. }
  83. #region private properties
  84. private KnobPointerStyles _knobPointerStyle = KnobPointerStyles.circle;
  85. private int _minimum = 0;
  86. private int _maximum = 25;
  87. private int _LargeChange = 5;
  88. private int _SmallChange = 1;
  89. private int _scaleDivisions;
  90. private int _scaleSubDivisions;
  91. private Font _scaleFont;
  92. private bool _scaleFontAutoSize = true;
  93. private bool _drawDivInside;
  94. private bool _showSmallScale = false;
  95. private bool _showLargeScale = true;
  96. private float _startAngle = 135;
  97. private float _endAngle = 405;
  98. private float deltaAngle;
  99. private int _mouseWheelBarPartitions = 10;
  100. private float drawRatio = 1;
  101. private float gradLength = 4;
  102. // Color of the pointer
  103. private Color _PointerColor = Color.SlateBlue;
  104. private Color _knobBackColor = Color.LightGray;
  105. private Color _scaleColor = Color.Black;
  106. private int _Value = 0;
  107. private bool isFocused = false;
  108. private bool isKnobRotating = false;
  109. private bool isMiddleArea = false;
  110. private Rectangle rKnob;
  111. private Rectangle rMiddleArea;
  112. private Point pKnob;
  113. private Pen DottedPen;
  114. Brush brushKnob;
  115. Brush brushMiddleArea;
  116. Brush brushKnobPointer;
  117. private Font knobFont;
  118. //-------------------------------------------------------
  119. // declare Off screen image and Offscreen graphics
  120. //-------------------------------------------------------
  121. private Image OffScreenImage;
  122. private Graphics gOffScreen;
  123. #endregion
  124. #region event
  125. //-------------------------------------------------------
  126. // An event that clients can use to be notified whenever
  127. // the Value is Changed.
  128. //-------------------------------------------------------
  129. public event ValueChangedEventHandler ValueChanged;
  130. //-------------------------------------------------------
  131. // Invoke the ValueChanged event; called when value
  132. // is changed
  133. //-------------------------------------------------------
  134. protected virtual void OnValueChanged(object sender) {
  135. ValueChanged?.Invoke(sender);
  136. }
  137. //-------------------------------------------------------
  138. // An event that clients can use to be notified whenever
  139. // the middle area of this control is clicked.
  140. //-------------------------------------------------------
  141. public event MiddleAreaMouseClickEventHandler MiddleAreaClicked;
  142. //-------------------------------------------------------
  143. // Invoke the M event; called when value
  144. // is changed
  145. //-------------------------------------------------------
  146. protected virtual void OnMiddleAreaClicked(object sender) {
  147. MiddleAreaClicked?.Invoke(sender);
  148. }
  149. #endregion
  150. #region (* public Properties *)
  151. /// <summary>
  152. /// Font of graduations
  153. /// </summary>
  154. [Description("Font of graduations")]
  155. [Category("KnobControl")]
  156. public Font ScaleFont {
  157. get { return _scaleFont; }
  158. set {
  159. _scaleFont = value;
  160. // Redraw
  161. SetDimensions();
  162. Invalidate();
  163. }
  164. }
  165. /// <summary>
  166. /// Autosize or not for font of graduations
  167. /// </summary>
  168. [Description("Autosize Font of graduations")]
  169. [Category("KnobControl")]
  170. [DefaultValue(true)]
  171. public bool ScaleFontAutoSize {
  172. get { return _scaleFontAutoSize; }
  173. set {
  174. _scaleFontAutoSize = value;
  175. // Redraw
  176. SetDimensions();
  177. Invalidate();
  178. }
  179. }
  180. /// <summary>
  181. /// Start angle to display graduations
  182. /// </summary>
  183. /// <value>The start angle to display graduations.</value>
  184. [Description("Set the start angle to display graduations (min 90)")]
  185. [Category("KnobControl")]
  186. [DefaultValue(135)]
  187. public float StartAngle {
  188. get { return _startAngle; }
  189. set {
  190. if (value >= 90 && value < _endAngle) {
  191. _startAngle = value;
  192. deltaAngle = _endAngle - StartAngle;
  193. // Redraw
  194. Invalidate();
  195. }
  196. }
  197. }
  198. /// <summary>
  199. /// End angle to display graduations
  200. /// </summary>
  201. /// <value>The end angle to display graduations.</value>
  202. [Description("Set the end angle to display graduations (max 450)")]
  203. [Category("KnobControl")]
  204. [DefaultValue(405)]
  205. public float EndAngle {
  206. get { return _endAngle; }
  207. set {
  208. if (value <= 450 && value > _startAngle) {
  209. _endAngle = value;
  210. deltaAngle = _endAngle - _startAngle;
  211. // Redraw
  212. Invalidate();
  213. }
  214. }
  215. }
  216. /// <summary>
  217. /// Style of pointer: circle or line
  218. /// </summary>
  219. [Description("Set the style of the knob pointer: a circle or a line")]
  220. [Category("KnobControl")]
  221. public KnobPointerStyles KnobPointerStyle {
  222. get { return _knobPointerStyle; }
  223. set {
  224. _knobPointerStyle = value;
  225. // Redraw
  226. Invalidate();
  227. }
  228. }
  229. /// <summary>
  230. /// Gets or sets the mouse wheel bar partitions.
  231. /// </summary>
  232. /// <value>The mouse wheel bar partitions.</value>
  233. /// <exception cref="T:System.ArgumentOutOfRangeException">exception thrown when value isn't greather than zero</exception>
  234. [Description("Set to how many parts is bar divided when using mouse wheel")]
  235. [Category("KnobControl")]
  236. [DefaultValue(10)]
  237. public int MouseWheelBarPartitions {
  238. get { return _mouseWheelBarPartitions; }
  239. set {
  240. if (value > 0)
  241. _mouseWheelBarPartitions = value;
  242. else throw new ArgumentOutOfRangeException("MouseWheelBarPartitions has to be greather than zero");
  243. }
  244. }
  245. /// <summary>
  246. /// Draw string graduations inside or outside knob circle
  247. /// </summary>
  248. ///
  249. [Description("Draw graduation strings inside or outside the knob circle")]
  250. [Category("KnobControl")]
  251. [DefaultValue(false)]
  252. public bool DrawDivInside {
  253. get { return _drawDivInside; }
  254. set {
  255. _drawDivInside = value;
  256. // Redraw
  257. SetDimensions();
  258. Invalidate();
  259. }
  260. }
  261. /// <summary>
  262. /// Color of graduations
  263. /// </summary>
  264. [Description("Color of graduations")]
  265. [Category("KnobControl")]
  266. public Color ScaleColor {
  267. get { return _scaleColor; }
  268. set {
  269. _scaleColor = value;
  270. // Redraw
  271. Invalidate();
  272. }
  273. }
  274. /// <summary>
  275. /// Color of graduations
  276. /// </summary>
  277. [Description("Color of knob")]
  278. [Category("KnobControl")]
  279. public Color KnobBackColor {
  280. get { return _knobBackColor; }
  281. set {
  282. _knobBackColor = value;
  283. SetDimensions();
  284. // Redraw
  285. Invalidate();
  286. }
  287. }
  288. /// <summary>
  289. /// How many divisions of maximum?
  290. /// </summary>
  291. [Description("Set the number of intervals between minimum and maximum")]
  292. [Category("KnobControl")]
  293. public int ScaleDivisions {
  294. get { return _scaleDivisions; }
  295. set {
  296. if (value > 1) {
  297. _scaleDivisions = value;
  298. // Redraw
  299. Invalidate();
  300. }
  301. }
  302. }
  303. /// <summary>
  304. /// How many subdivisions for each division
  305. /// </summary>
  306. [Description("Set the number of subdivisions between main divisions of graduation.")]
  307. [Category("KnobControl")]
  308. public int ScaleSubDivisions {
  309. get { return _scaleSubDivisions; }
  310. set {
  311. if (value > 0 && _scaleDivisions > 0 && (_maximum - _minimum) / (value * _scaleDivisions) > 0) {
  312. _scaleSubDivisions = value;
  313. // Redraw
  314. Invalidate();
  315. }
  316. }
  317. }
  318. /// <summary>
  319. /// Shows Small Scale marking.
  320. /// </summary>
  321. [Description("Show or hide subdivisions of graduations")]
  322. [Category("KnobControl")]
  323. public bool ShowSmallScale {
  324. get { return _showSmallScale; }
  325. set {
  326. if (value == true) {
  327. if (_scaleDivisions > 0 && _scaleSubDivisions > 0 && (_maximum - _minimum) / (_scaleSubDivisions * _scaleDivisions) > 0) {
  328. _showSmallScale = value;
  329. // Redraw
  330. Invalidate();
  331. }
  332. } else {
  333. _showSmallScale = value;
  334. // Redraw
  335. Invalidate();
  336. }
  337. }
  338. }
  339. /// <summary>
  340. /// Shows Large Scale marking
  341. /// </summary>
  342. [Description("Show or hide graduations")]
  343. [Category("KnobControl")]
  344. public bool ShowLargeScale {
  345. get { return _showLargeScale; }
  346. set {
  347. _showLargeScale = value;
  348. // need to redraw
  349. SetDimensions();
  350. // Redraw
  351. Invalidate();
  352. }
  353. }
  354. /// <summary>
  355. /// Minimum Value for knob Control
  356. /// </summary>
  357. [Description("set the minimum value for the knob control")]
  358. [Category("KnobControl")]
  359. public int Minimum {
  360. get { return _minimum; }
  361. set {
  362. _minimum = value;
  363. // Redraw
  364. Invalidate();
  365. }
  366. }
  367. /// <summary>
  368. /// Maximum value for knob control
  369. /// </summary>
  370. [Description("set the maximum value for the knob control")]
  371. [Category("KnobControl")]
  372. public int Maximum {
  373. get { return _maximum; }
  374. set {
  375. if (value > _minimum) {
  376. _maximum = value;
  377. if (_scaleSubDivisions > 0 && _scaleDivisions > 0 && (_maximum - _minimum) / (_scaleSubDivisions * _scaleDivisions) <= 0) {
  378. _showSmallScale = false;
  379. }
  380. SetDimensions();
  381. // Redraw
  382. Invalidate();
  383. }
  384. }
  385. }
  386. /// <summary>
  387. /// value set for large change
  388. /// </summary>
  389. [Description("set the value for the large changes")]
  390. [Category("KnobControl")]
  391. public int LargeChange {
  392. get { return _LargeChange; }
  393. set {
  394. _LargeChange = value;
  395. // Redraw
  396. Invalidate();
  397. }
  398. }
  399. /// <summary>
  400. /// value set for small change.
  401. /// </summary>
  402. [Description("set the minimum value for the small changes")]
  403. [Category("KnobControl")]
  404. public int SmallChange {
  405. get { return _SmallChange; }
  406. set {
  407. _SmallChange = value;
  408. // Redraw
  409. Invalidate();
  410. }
  411. }
  412. /// <summary>
  413. /// Current Value of knob control
  414. /// </summary>
  415. [Description("set the current value of the knob control")]
  416. [Category("KnobControl")]
  417. public int Value {
  418. get { return _Value; }
  419. set {
  420. if (value >= _minimum && value <= _maximum) {
  421. _Value = value;
  422. // Redraw
  423. Invalidate();
  424. // call delegate
  425. OnValueChanged(this);
  426. }
  427. }
  428. }
  429. /// <summary>
  430. /// Color of the button
  431. /// </summary>
  432. [Description("set the color of the pointer")]
  433. [Category("KnobControl")]
  434. public Color PointerColor {
  435. get { return _PointerColor; }
  436. set {
  437. _PointerColor = value;
  438. SetDimensions();
  439. // Redraw
  440. Invalidate();
  441. }
  442. }
  443. #endregion properties
  444. public KnobControl() {
  445. // This call is required by the Windows.Forms Form Designer.
  446. DottedPen = new Pen(Utility.GetDarkColor(this.BackColor, 40)) {
  447. DashStyle = System.Drawing.Drawing2D.DashStyle.Dash,
  448. DashCap = System.Drawing.Drawing2D.DashCap.Flat
  449. };
  450. InitializeComponent();
  451. knobFont = new Font(this.Font.FontFamily, this.Font.Size);
  452. _scaleFont = new Font(this.Font.FontFamily, this.Font.Size);
  453. // Properties initialisation
  454. // "start angle" and "end angle" possible values:
  455. // 90 = bottom (minimum value for "start angle")
  456. // 180 = left
  457. // 270 = top
  458. // 360 = right
  459. // 450 = bottom again (maximum value for "end angle")
  460. // So the couple (90, 450) will give an entire circle and the couple (180, 360) will give half a circle.
  461. _startAngle = 135;
  462. _endAngle = 405;
  463. deltaAngle = _endAngle - _startAngle;
  464. _minimum = 0;
  465. _maximum = 100;
  466. _scaleDivisions = 11;
  467. _scaleSubDivisions = 4;
  468. _mouseWheelBarPartitions = 10;
  469. _scaleColor = Color.Black;
  470. _knobBackColor = Color.White;
  471. SetDimensions();
  472. }
  473. #region override
  474. /// <summary>
  475. /// Paint event: draw all
  476. /// </summary>
  477. /// <param name="e"></param>
  478. protected override void OnPaint(PaintEventArgs e) {
  479. // Set antialias effect on
  480. gOffScreen.SmoothingMode = SmoothingMode.AntiAlias;
  481. Graphics g = e.Graphics;
  482. // Set background color of Image...
  483. gOffScreen.Clear(this.BackColor);
  484. if (isMiddleArea) {
  485. // Fill knob Background to give knob effect....
  486. gOffScreen.FillEllipse(brushKnob, Utility.shrinkRectangle(rKnob, 4));
  487. // Fill middle area Background to give bulge effect....
  488. gOffScreen.FillEllipse(brushMiddleArea, Utility.shrinkRectangle(rMiddleArea, 4));
  489. // Draw border of knob
  490. gOffScreen.DrawEllipse(new Pen(Color.DarkGray, 2f), Utility.shrinkRectangle(rKnob, 4));
  491. } else {
  492. gOffScreen.FillEllipse(brushKnob, rKnob);
  493. gOffScreen.FillEllipse(brushMiddleArea, rMiddleArea);
  494. gOffScreen.DrawEllipse(new Pen(this.BackColor), rKnob);
  495. }
  496. //if control is focused
  497. if (this.isFocused) {
  498. gOffScreen.DrawEllipse(DottedPen, rKnob);
  499. }
  500. // DrawPointer
  501. DrawPointer(gOffScreen);
  502. //---------------------------------------------
  503. // draw small and large scale
  504. //---------------------------------------------
  505. DrawDivisions(gOffScreen, rKnob);
  506. // Draw image on screen
  507. g.DrawImage(OffScreenImage, 0, 0);
  508. }
  509. protected override void OnPaintBackground(PaintEventArgs e) {
  510. // Empty To avoid Flickring due do background Drawing.
  511. }
  512. /// <summary>
  513. /// Mouse down event: select control
  514. /// </summary>
  515. /// <param name="e"></param>
  516. protected override void OnMouseDown(MouseEventArgs e) {
  517. if (Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) {
  518. if (isFocused) {
  519. // was already selected
  520. // Start Rotation of knob only if it was selected before
  521. isKnobRotating = true;
  522. if (Utility.IsPointInCircle(new Point(e.X, e.Y), new Point(rKnob.Left + (rKnob.Right - rKnob.Left) / 2, rKnob.Top + (rKnob.Bottom - rKnob.Top) / 2), rKnob.Width / 3)) {
  523. isMiddleArea = true;
  524. Invalidate();
  525. }
  526. } else {
  527. // Was not selected before => select it
  528. Focus();
  529. isFocused = true;
  530. isKnobRotating = false; // disallow rotation, must click again
  531. isMiddleArea = false;
  532. // draw dotted border to show that it is selected
  533. Invalidate();
  534. }
  535. }
  536. }
  537. //----------------------------------------------------------
  538. // we need to override IsInputKey method to allow user to
  539. // use up, down, right and bottom keys other wise using this
  540. // keys will change focus from current object to another
  541. // object on the form
  542. //----------------------------------------------------------
  543. protected override bool IsInputKey(Keys key) {
  544. switch (key) {
  545. case Keys.Up:
  546. case Keys.Down:
  547. case Keys.Right:
  548. case Keys.Left:
  549. return true;
  550. }
  551. return base.IsInputKey(key);
  552. }
  553. /// <summary>
  554. /// Mouse up event: display new value
  555. /// </summary>
  556. /// <param name="e"></param>
  557. protected override void OnMouseUp(MouseEventArgs e) {
  558. if (Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) {
  559. if (isFocused == true && isKnobRotating == true && !isMiddleArea) {
  560. // Change value is allowed only only after 2nd click
  561. this.Value = this.GetValueFromPosition(new Point(e.X, e.Y));
  562. } else {
  563. // 1st click = only focus
  564. isFocused = true;
  565. isKnobRotating = true;
  566. }
  567. if (isMiddleArea) {
  568. if (isFocused && Utility.IsPointInCircle(new Point(e.X, e.Y), new Point(rKnob.Left + (rKnob.Right - rKnob.Left) / 2, rKnob.Top + (rKnob.Bottom - rKnob.Top) / 2), rKnob.Width / 3)) {
  569. OnMiddleAreaClicked(this);
  570. }
  571. isMiddleArea = false;
  572. Invalidate();
  573. }
  574. }
  575. this.Cursor = Cursors.Default;
  576. }
  577. /// <summary>
  578. /// Mouse move event: drag the pointer to the mouse position
  579. /// </summary>
  580. /// <param name="e"></param>
  581. protected override void OnMouseMove(MouseEventArgs e) {
  582. //--------------------------------------
  583. // Following Handles Knob Rotating
  584. //--------------------------------------
  585. if (e.Button == MouseButtons.Left && this.isKnobRotating == true && !isMiddleArea) {
  586. this.Cursor = Cursors.Hand;
  587. Point p = new Point(e.X, e.Y);
  588. int posVal = this.GetValueFromPosition(p);
  589. Value = posVal;
  590. }
  591. }
  592. /// <summary>
  593. /// Mousewheel: change value
  594. /// </summary>
  595. /// <param name="e"></param>
  596. protected override void OnMouseWheel(MouseEventArgs e) {
  597. base.OnMouseWheel(e);
  598. if (isFocused && isKnobRotating && !isMiddleArea && Utility.IsPointinRectangle(new Point(e.X, e.Y), rKnob)) {
  599. // the Delta value is always 120, as explained in MSDN
  600. int v = (e.Delta / 120) * (_maximum - _minimum) / _mouseWheelBarPartitions;
  601. SetProperValue(Value + v);
  602. // Avoid to send MouseWheel event to the parent container
  603. ((HandledMouseEventArgs)e).Handled = true;
  604. }
  605. }
  606. /// <summary>
  607. /// Leave event: disallow knob rotation
  608. /// </summary>
  609. /// <param name="e"></param>
  610. protected override void OnLeave(EventArgs e) {
  611. // unselect the control (remove dotted border)
  612. isFocused = false;
  613. isKnobRotating = false;
  614. Invalidate();
  615. base.OnLeave(new EventArgs());
  616. }
  617. /// <summary>
  618. /// Key down event: change value
  619. /// </summary>
  620. /// <param name="e"></param>
  621. protected override void OnKeyDown(KeyEventArgs e) {
  622. if (isFocused) {
  623. //--------------------------------------------------------
  624. // Handles knob rotation with up,down,left and right keys
  625. //--------------------------------------------------------
  626. if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Right) {
  627. if (_Value < _maximum) Value = _Value + 1;
  628. this.Refresh();
  629. } else if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Left) {
  630. if (_Value > _minimum) Value = _Value - 1;
  631. this.Refresh();
  632. }
  633. }
  634. }
  635. /// <summary>
  636. /// Clean up any resources being used.
  637. /// </summary>
  638. protected override void Dispose(bool disposing) {
  639. if (disposing) {
  640. if (components != null) {
  641. components.Dispose();
  642. }
  643. }
  644. base.Dispose(disposing);
  645. }
  646. #endregion
  647. #region Component Designer generated code
  648. /// <summary>
  649. /// Required method for Designer support - do not modify
  650. /// the contents of this method with the code editor.
  651. /// </summary>
  652. private void InitializeComponent() {
  653. //
  654. // KnobControl
  655. //
  656. this.ImeMode = System.Windows.Forms.ImeMode.On;
  657. this.Name = "KnobControl";
  658. this.Resize += new System.EventHandler(this.KnobControl_Resize);
  659. }
  660. #endregion
  661. #region Draw
  662. /// <summary>
  663. /// Draw the pointer of the knob (a small button inside the main button)
  664. /// </summary>
  665. /// <param name="Gr"></param>
  666. private void DrawPointer(Graphics Gr) {
  667. try {
  668. float radius = (float)(rKnob.Width / 2);
  669. // Draw a line
  670. if (_knobPointerStyle == KnobPointerStyles.line) {
  671. int l = (int)radius / 2;
  672. int w = l / 4;
  673. Point[] pt = GetKnobLine(Gr, l);
  674. Gr.DrawLine(new Pen(_PointerColor, w), pt[0], pt[1]);
  675. } else {
  676. // Draw a circle
  677. int w = 0;
  678. int h = 0;
  679. int l = 0;
  680. string strvalmax = _maximum.ToString();
  681. string strvalmin = _minimum.ToString();
  682. string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin;
  683. double val = Convert.ToDouble(strval);
  684. String str = String.Format("{0,0:D}", (int)val);
  685. float fSize;
  686. SizeF strsize;
  687. if (_scaleFontAutoSize) {
  688. // Use font family = _scaleFont, but size = automatic
  689. fSize = (float)(6F * drawRatio);
  690. if (fSize < 6)
  691. fSize = 6;
  692. strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, fSize));
  693. } else {
  694. // Use font family = _scaleFont, but size = fixed
  695. fSize = _scaleFont.Size;
  696. strsize = Gr.MeasureString(str, _scaleFont);
  697. }
  698. int strw = (int)strsize.Width;
  699. int strh = (int)strsize.Height;
  700. //w = Math.Max(strw, strh);
  701. w = strh;
  702. // radius of small circle
  703. l = (int)radius - w / 2;
  704. h = w;
  705. if (isMiddleArea) {
  706. Point Arrow = this.GetKnobPosition(l - 7); // Remove 2 pixels to offset the small circle inside the knob
  707. // Draw pointer arrow that shows knob position
  708. Rectangle rPointer = new Rectangle(Arrow.X - w / 2 + 1, Arrow.Y - w / 2 + 1, w - 2, h - 2);
  709. //Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(_PointerColor));
  710. Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(Utility.GetLightColor(_PointerColor, 55)));
  711. Gr.FillEllipse(brushKnobPointer, rPointer);
  712. } else {
  713. Point Arrow = this.GetKnobPosition(l - 5); // Remove 2 pixels to offset the small circle inside the knob
  714. // Draw pointer arrow that shows knob position
  715. Rectangle rPointer = new Rectangle(Arrow.X - w / 2, Arrow.Y - w / 2, w, h);
  716. //Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(_PointerColor));
  717. Utility.DrawInsetCircle(ref Gr, rPointer, new Pen(Utility.GetLightColor(_PointerColor, 55)));
  718. Gr.FillEllipse(brushKnobPointer, rPointer);
  719. }
  720. }
  721. } catch (Exception ex) {
  722. Console.Write(ex.Message);
  723. }
  724. }
  725. /// <summary>
  726. /// Draw graduations
  727. /// </summary>
  728. /// <param name="Gr"></param>
  729. /// <param name="rc">Knob rectangle</param>
  730. /// <returns></returns>
  731. private bool DrawDivisions(Graphics Gr, RectangleF rc) {
  732. if (this == null)
  733. return false;
  734. float cx = pKnob.X;
  735. float cy = pKnob.Y;
  736. float w = rc.Width;
  737. float h = rc.Height;
  738. float tx;
  739. float ty;
  740. float incr = Utility.GetRadian((_endAngle - _startAngle) / ((_scaleDivisions - 1) * (_scaleSubDivisions + 1)));
  741. float currentAngle = Utility.GetRadian(_startAngle);
  742. float radius = (float)(rc.Width / 2);
  743. float rulerValue = (float)_minimum;
  744. Font font;
  745. Pen penL = new Pen(_scaleColor, (2 * drawRatio));
  746. Pen penS = new Pen(_scaleColor, (1 * drawRatio));
  747. SolidBrush br = new SolidBrush(_scaleColor);
  748. PointF ptStart = new PointF(0, 0);
  749. PointF ptEnd = new PointF(0, 0);
  750. int n = 0;
  751. if (_showLargeScale) {
  752. // Size of maxi string
  753. string strvalmax = _maximum.ToString();
  754. string strvalmin = _minimum.ToString();
  755. string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin;
  756. double val = Convert.ToDouble(strval);
  757. //double val = _maximum;
  758. String str = String.Format("{0,0:D}", (int)val);
  759. float fSize;
  760. SizeF strsize;
  761. if (_scaleFontAutoSize) {
  762. fSize = (float)(6F * drawRatio);
  763. if (fSize < 6)
  764. fSize = 6;
  765. } else {
  766. fSize = _scaleFont.Size;
  767. }
  768. font = new Font(_scaleFont.FontFamily, fSize);
  769. strsize = Gr.MeasureString(str, font);
  770. int strw = (int)strsize.Width;
  771. int strh = (int)strsize.Height;
  772. int wmax = Math.Max(strw, strh);
  773. float l = 0;
  774. gradLength = 2 * drawRatio;
  775. for (; n < _scaleDivisions; n++) {
  776. // draw divisions
  777. ptStart.X = (float)(cx + (radius) * Math.Cos(currentAngle));
  778. ptStart.Y = (float)(cy + (radius) * Math.Sin(currentAngle));
  779. ptEnd.X = (float)(cx + (radius + gradLength) * Math.Cos(currentAngle));
  780. ptEnd.Y = (float)(cy + (radius + gradLength) * Math.Sin(currentAngle));
  781. Gr.DrawLine(penL, ptStart, ptEnd);
  782. //Draw graduation values
  783. val = Math.Round(rulerValue);
  784. str = String.Format("{0,0:D}", (int)val);
  785. // If autosize
  786. if (_scaleFontAutoSize)
  787. strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, fSize));
  788. else
  789. strsize = Gr.MeasureString(str, new Font(_scaleFont.FontFamily, _scaleFont.Size));
  790. if (_drawDivInside) {
  791. // graduations values inside the knob
  792. l = (int)radius - (wmax / 2) - 2;
  793. tx = (float)(cx + l * Math.Cos(currentAngle));
  794. ty = (float)(cy + l * Math.Sin(currentAngle));
  795. } else {
  796. // graduation values outside the knob
  797. //l = (Width / 2) - (wmax / 2) ;
  798. l = radius + gradLength + wmax / 2;
  799. tx = (float)(cx + l * Math.Cos(currentAngle));
  800. ty = (float)(cy + l * Math.Sin(currentAngle));
  801. }
  802. Gr.DrawString(str,
  803. font,
  804. br,
  805. tx - (float)(strsize.Width * 0.5),
  806. ty - (float)(strsize.Height * 0.5));
  807. rulerValue += (float)((_maximum - _minimum) / (_scaleDivisions - 1));
  808. if (n == _scaleDivisions - 1) {
  809. break;
  810. }
  811. // Subdivisions
  812. #region SubDivisions
  813. if (_scaleDivisions <= 0)
  814. currentAngle += incr;
  815. else {
  816. for (int j = 0; j <= _scaleSubDivisions; j++) {
  817. currentAngle += incr;
  818. // if user want to display small graduations
  819. if (_showSmallScale) {
  820. ptStart.X = (float)(cx + radius * Math.Cos(currentAngle));
  821. ptStart.Y = (float)(cy + radius * Math.Sin(currentAngle));
  822. ptEnd.X = (float)(cx + (radius + gradLength / 2) * Math.Cos(currentAngle));
  823. ptEnd.Y = (float)(cy + (radius + gradLength / 2) * Math.Sin(currentAngle));
  824. Gr.DrawLine(penS, ptStart, ptEnd);
  825. }
  826. }
  827. }
  828. #endregion
  829. }
  830. font.Dispose();
  831. }
  832. return true;
  833. }
  834. /// <summary>
  835. /// Set position of button inside its rectangle to insure that divisions will fit.
  836. /// </summary>
  837. private void SetDimensions() {
  838. Font font;
  839. // Rectangle
  840. float x, y, w, h;
  841. x = 0;
  842. y = 0;
  843. w = h = Width;
  844. // Calculate ratio
  845. drawRatio = w / 150;
  846. if (drawRatio == 0.0)
  847. drawRatio = 1;
  848. if (_showLargeScale) {
  849. Graphics Gr = this.CreateGraphics();
  850. string strvalmax = _maximum.ToString();
  851. string strvalmin = _minimum.ToString();
  852. string strval = strvalmax.Length > strvalmin.Length ? strvalmax : strvalmin;
  853. double val = Convert.ToDouble(strval);
  854. //double val = _maximum;
  855. String str = String.Format("{0,0:D}", (int)val);
  856. float fSize = _scaleFont.Size;
  857. if (_scaleFontAutoSize) {
  858. fSize = (float)(6F * drawRatio);
  859. if (fSize < 6)
  860. fSize = 6;
  861. font = new Font(_scaleFont.FontFamily, fSize);
  862. } else {
  863. fSize = _scaleFont.Size;
  864. font = new Font(_scaleFont.FontFamily, _scaleFont.Size);
  865. }
  866. SizeF strsize = Gr.MeasureString(str, font);
  867. // Graduations outside
  868. gradLength = 4 * drawRatio;
  869. if (_drawDivInside) {
  870. // Graduations inside : remove only 2*8 pixels
  871. //x = y = 8;
  872. x = y = gradLength;
  873. w = Width - 2 * x;
  874. } else {
  875. // remove 2 * size of text and length of graduation
  876. //gradLength = 4 * drawRatio;
  877. int strw = (int)strsize.Width;
  878. int strh = (int)strsize.Height;
  879. int max = Math.Max(strw, strh);
  880. x = max;
  881. y = max;
  882. w = (int)(Width - 2 * max - gradLength);
  883. }
  884. if (w <= 0)
  885. w = 1;
  886. h = w;
  887. // Rectangle of the rounded knob
  888. this.rKnob = new Rectangle((int)x, (int)y, (int)w, (int)h);
  889. // Rectangle of the middle area of the knob
  890. this.rMiddleArea = new Rectangle((int)(x + w / 3), (int)(y + h / 3), (int)(w / 3), (int)(h / 3));
  891. Gr.Dispose();
  892. } else {
  893. this.rKnob = new Rectangle(0, 0, Width, Height);
  894. this.rMiddleArea = new Rectangle((int)(w / 3), (int)(h / 3), (int)(w / 3), (int)(h / 3));
  895. }
  896. // Center of knob
  897. this.pKnob = new Point(rKnob.X + rKnob.Width / 2, rKnob.Y + rKnob.Height / 2);
  898. // create offscreen image
  899. this.OffScreenImage = new Bitmap(this.Width, this.Height);
  900. // create offscreen graphics
  901. this.gOffScreen = Graphics.FromImage(OffScreenImage);
  902. // Depends on retangle dimensions
  903. // create LinearGradientBrush for creating knob
  904. brushKnob = new LinearGradientBrush(
  905. rKnob, Utility.GetLightColor(_knobBackColor, 55), Utility.GetDarkColor(_knobBackColor, 55), LinearGradientMode.ForwardDiagonal);
  906. // create LinearGradientBrush for creating knob
  907. brushMiddleArea = new LinearGradientBrush(
  908. rMiddleArea, Utility.GetDarkColor(_knobBackColor, 55), Utility.GetLightColor(_knobBackColor, 55), LinearGradientMode.ForwardDiagonal);
  909. // create LinearGradientBrush for knobPointer
  910. brushKnobPointer = new LinearGradientBrush(
  911. rKnob, Utility.GetLightColor(_PointerColor, 55), Utility.GetDarkColor(_PointerColor, 55), LinearGradientMode.ForwardDiagonal);
  912. }
  913. #endregion
  914. #region resize
  915. /// <summary>
  916. /// Resize event
  917. /// </summary>
  918. /// <param name="sender"></param>
  919. /// <param name="e"></param>
  920. private void KnobControl_Resize(object sender, System.EventArgs e) {
  921. // Control remains square
  922. Height = Width;
  923. SetDimensions();
  924. Invalidate();
  925. }
  926. #endregion
  927. #region private functions
  928. /// <summary>
  929. /// Sets the trackbar value so that it wont exceed allowed range.
  930. /// </summary>
  931. /// <param name="val">The value.</param>
  932. private void SetProperValue(int val) {
  933. if (val < _minimum) Value = _minimum;
  934. else if (val > _maximum) Value = _maximum;
  935. else Value = val;
  936. }
  937. /// <summary>
  938. /// gets knob position that is to be drawn on control minus a small amount in order that the knob position stay inside the circle.
  939. /// </summary>
  940. /// <returns>Point that describes current knob position</returns>
  941. private Point GetKnobPosition(int l) {
  942. float cx = pKnob.X;
  943. float cy = pKnob.Y;
  944. // FAB: 21/08/18
  945. float degree = deltaAngle * (this.Value - _minimum) / (_maximum - _minimum);
  946. degree = Utility.GetRadian(degree + _startAngle);
  947. Point Pos = new Point(0, 0) {
  948. X = (int)(cx + l * Math.Cos(degree)),
  949. Y = (int)(cy + l * Math.Sin(degree))
  950. };
  951. return Pos;
  952. }
  953. /// <summary>
  954. /// return 2 points of a line starting from the center of the knob to the periphery
  955. /// </summary>
  956. /// <param name="l"></param>
  957. /// <returns></returns>
  958. private Point[] GetKnobLine(Graphics Gr, int l) {
  959. Point[] pret = new Point[2];
  960. float cx = pKnob.X;
  961. float cy = pKnob.Y;
  962. float radius = (float)(rKnob.Width / 2);
  963. // FAB: 21/08/18
  964. float degree = deltaAngle * (this.Value - _minimum) / (_maximum - _minimum);
  965. degree = Utility.GetRadian(degree + _startAngle);
  966. double val = _maximum;
  967. String str = String.Format("{0,0:D}", (int)val);
  968. float fSize;
  969. SizeF strsize;
  970. if (!_scaleFontAutoSize) {
  971. fSize = _scaleFont.Size;
  972. strsize = Gr.MeasureString(str, _scaleFont);
  973. } else {
  974. fSize = (float)(6F * drawRatio);
  975. if (fSize < 6)
  976. fSize = 6;
  977. knobFont = new Font(_scaleFont.FontFamily, fSize);
  978. strsize = Gr.MeasureString(str, knobFont);
  979. }
  980. int strw = (int)strsize.Width;
  981. int strh = (int)strsize.Height;
  982. int w = Math.Max(strw, strh);
  983. Point Pos = new Point(0, 0);
  984. if (_drawDivInside) {
  985. // Center (from)
  986. Pos.X = (int)(cx + (radius / 10) * Math.Cos(degree));
  987. Pos.Y = (int)(cy + (radius / 10) * Math.Sin(degree));
  988. pret[0] = new Point(Pos.X, Pos.Y);
  989. // External (to)
  990. Pos.X = (int)(cx + (radius - w) * Math.Cos(degree));
  991. Pos.Y = (int)(cy + (radius - w) * Math.Sin(degree));
  992. pret[1] = new Point(Pos.X, Pos.Y);
  993. } else {
  994. // Internal (from)
  995. Pos.X = (int)(cx + (radius - drawRatio * 10 - l) * Math.Cos(degree));
  996. Pos.Y = (int)(cy + (radius - drawRatio * 10 - l) * Math.Sin(degree));
  997. pret[0] = new Point(Pos.X, Pos.Y);
  998. // External (to)
  999. Pos.X = (int)(cx + (radius - 4) * Math.Cos(degree));
  1000. Pos.Y = (int)(cy + (radius - 4) * Math.Sin(degree));
  1001. pret[1] = new Point(Pos.X, Pos.Y);
  1002. }
  1003. return pret;
  1004. }
  1005. /// <summary>
  1006. /// converts geometrical position into value..
  1007. /// </summary>
  1008. /// <param name="p">Point that is to be converted</param>
  1009. /// <returns>Value derived from position</returns>
  1010. private int GetValueFromPosition(Point p) {
  1011. float degree = 0;
  1012. int v = 0;
  1013. if (p.X <= pKnob.X) {
  1014. degree = (float)(pKnob.Y - p.Y) / (float)(pKnob.X - p.X);
  1015. degree = (float)Math.Atan(degree);
  1016. degree = (degree) * (float)(180 / Math.PI) + (180 - _startAngle);
  1017. } else if (p.X > pKnob.X) {
  1018. degree = (float)(p.Y - pKnob.Y) / (float)(p.X - pKnob.X);
  1019. degree = (float)Math.Atan(degree);
  1020. degree = (degree) * (float)(180 / Math.PI) + 360 - _startAngle;
  1021. }
  1022. // round to the nearest value (when you click just before or after a graduation!)
  1023. // FAB: 25/08/18
  1024. v = _minimum + (int)Math.Round(degree * (_maximum - _minimum) / deltaAngle);
  1025. if (v > _maximum) v = _maximum;
  1026. if (v < _minimum) v = _minimum;
  1027. return v;
  1028. }
  1029. #endregion
  1030. }
  1031. }