UserGaugeChart.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using System.Drawing.Drawing2D;
  10. namespace HslCommunication.Controls
  11. {
  12. /// <summary>
  13. /// 仪表盘控件类
  14. /// </summary>
  15. public partial class UserGaugeChart : UserControl
  16. {
  17. /// <summary>
  18. /// 实例化一个仪表盘控件
  19. /// </summary>
  20. public UserGaugeChart( )
  21. {
  22. InitializeComponent( );
  23. pen_gauge_border = new Pen( color_gauge_border );
  24. brush_gauge_pointer = new SolidBrush( color_gauge_pointer );
  25. centerFormat = new StringFormat( );
  26. centerFormat.Alignment = StringAlignment.Center;
  27. centerFormat.LineAlignment = StringAlignment.Center;
  28. pen_gauge_alarm = new Pen( Color.OrangeRed, 3f );
  29. pen_gauge_alarm.DashStyle = DashStyle.Custom;
  30. pen_gauge_alarm.DashPattern = new float[] { 5, 1 };
  31. hybirdLock = new Core.SimpleHybirdLock( );
  32. m_UpdateAction = new Action( Invalidate );
  33. timer_alarm_check = new Timer( );
  34. timer_alarm_check.Tick += Timer_alarm_check_Tick;
  35. timer_alarm_check.Interval = 1000;
  36. DoubleBuffered = true;
  37. }
  38. private void Timer_alarm_check_Tick( object sender, EventArgs e )
  39. {
  40. if (value_current > value_alarm_max || value_current < value_alarm_min)
  41. {
  42. alarm_check = !alarm_check;
  43. }
  44. else
  45. {
  46. alarm_check = false;
  47. }
  48. Invalidate( );
  49. }
  50. private void UserGaugeChart_Load( object sender, EventArgs e )
  51. {
  52. timer_alarm_check.Start( );
  53. }
  54. private void UserGaugeChart_Paint( object sender, PaintEventArgs e )
  55. {
  56. Graphics g = e.Graphics;
  57. g.SmoothingMode = SmoothingMode.HighQuality; // 消除锯齿
  58. g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; // 优化文本显示
  59. OperateResult<Point, int, double> setting = GetCenterPoint( );
  60. if (!setting.IsSuccess) return; // 不满足绘制条件
  61. Point center = setting.Content1;
  62. int radius = setting.Content2;
  63. float angle = Convert.ToSingle( setting.Content3 );
  64. Rectangle circular = new Rectangle( -radius, -radius, 2 * radius, 2 * radius );
  65. Rectangle circular_larger = new Rectangle( -radius - 5, -radius - 5, 2 * radius + 10, 2 * radius + 10 );
  66. Rectangle circular_mini = new Rectangle( -radius / 3, -radius / 3, 2 * radius / 3, 2 * radius / 3 );
  67. g.TranslateTransform( center.X, center.Y );
  68. g.DrawArc( pen_gauge_border, circular_mini, -angle, angle * 2 - 180 );
  69. g.DrawArc( pen_gauge_border, circular, angle - 180, 180 - angle * 2 );
  70. g.DrawLine( pen_gauge_border, (int)(-(radius / 3) * Math.Cos( angle / 180 * Math.PI )), -(int)((radius / 3) * Math.Sin( angle / 180 * Math.PI )), -(int)((radius - 30) * Math.Cos( angle / 180 * Math.PI )), -(int)((radius - 30) * Math.Sin( angle / 180 * Math.PI )) );
  71. g.DrawLine( pen_gauge_border, (int)((radius - 30) * Math.Cos( angle / 180 * Math.PI )), -(int)((radius - 30) * Math.Sin( angle / 180 * Math.PI )), (int)((radius / 3) * Math.Cos( angle / 180 * Math.PI )), -(int)((radius / 3) * Math.Sin( angle / 180 * Math.PI )) );
  72. // 开始绘制刻度
  73. g.RotateTransform( angle - 90 );
  74. int totle = segment_count;
  75. for (int i = 0; i <= totle; i++)
  76. {
  77. Rectangle rect = new Rectangle( -2, -radius, 3, 7 );
  78. g.FillRectangle( Brushes.DimGray, rect );
  79. rect = new Rectangle( -50, -radius + 7, 100, 20 );
  80. double current = ValueStart + (ValueMax - ValueStart) * i / totle;
  81. g.DrawString( current.ToString( ), Font, Brushes.DodgerBlue, rect, centerFormat );
  82. g.RotateTransform( (180 - 2 * angle) / totle / 2 );
  83. if (i != totle) g.DrawLine( Pens.DimGray, 0, -radius, 0, -radius + 3 );
  84. g.RotateTransform( (180 - 2 * angle) / totle / 2 );
  85. }
  86. g.RotateTransform( -(180 - 2 * angle) / totle );
  87. g.RotateTransform( angle - 90 );
  88. Rectangle text = new Rectangle( -36, -(radius * 2 / 3 - 3), 72, Font.Height + 3 );
  89. // 如果处于报警中,就闪烁
  90. if (value_current > value_alarm_max || value_current < value_alarm_min)
  91. {
  92. if (alarm_check)
  93. {
  94. g.FillRectangle( Brushes.OrangeRed, text );
  95. }
  96. }
  97. // g.FillRectangle(Brushes.Wheat, text);
  98. if (IsTextUnderPointer)
  99. {
  100. g.DrawString( Value.ToString( ), Font, Brushes.DimGray, text, centerFormat );
  101. // g.DrawRectangle(pen_gauge_border, text);
  102. text.Offset( 0, Font.Height );
  103. if (!string.IsNullOrEmpty( UnitText ))
  104. {
  105. g.DrawString( UnitText, Font, Brushes.Gray, text, centerFormat );
  106. }
  107. }
  108. g.RotateTransform( angle - 90 );
  109. g.RotateTransform( (float)((value_paint - ValueStart) / (ValueMax - ValueStart) * (180 - 2 * angle)) );
  110. Rectangle rectangle = new Rectangle( -5, -5, 10, 10 );
  111. g.FillEllipse( brush_gauge_pointer, rectangle );
  112. // g.DrawEllipse(Pens.Red, rectangle);
  113. Point[] points = new Point[] { new Point( 5, 0 ), new Point( 2, -radius + 40 ), new Point( 0, -radius + 20 ), new Point( -2, -radius + 40 ), new Point( -5, 0 ) };
  114. g.FillPolygon( brush_gauge_pointer, points );
  115. // g.DrawLines(Pens.Red, points);
  116. g.RotateTransform( (float)(-(value_paint - ValueStart) / (ValueMax - ValueStart) * (180 - 2 * angle)) );
  117. g.RotateTransform( 90 - angle );
  118. if (value_alarm_min > value_start && value_alarm_min <= value_max)
  119. {
  120. g.DrawArc( pen_gauge_alarm, circular_larger, angle - 180, (float)((ValueAlarmMin - ValueStart) / (ValueMax - ValueStart) * (180 - 2 * angle)) );
  121. }
  122. if (value_alarm_max >= value_start && value_alarm_max < value_max)
  123. {
  124. float angle_max = (float)((value_alarm_max - ValueStart) / (ValueMax - ValueStart) * (180 - 2 * angle));
  125. g.DrawArc( pen_gauge_alarm, circular_larger, -180 + angle + angle_max, 180 - 2 * angle - angle_max );
  126. }
  127. if (!IsTextUnderPointer)
  128. {
  129. g.DrawString( Value.ToString( ), Font, Brushes.DimGray, text, centerFormat );
  130. // g.DrawRectangle(pen_gauge_border, text);
  131. text.Offset( 0, Font.Height );
  132. if (!string.IsNullOrEmpty( UnitText ))
  133. {
  134. g.DrawString( UnitText, Font, Brushes.Gray, text, centerFormat );
  135. }
  136. }
  137. g.ResetTransform( );
  138. }
  139. /// <summary>
  140. /// 获取中心点的坐标
  141. /// </summary>
  142. /// <returns></returns>
  143. private OperateResult<Point, int, double> GetCenterPoint( )
  144. {
  145. OperateResult<Point, int, double> result = new OperateResult<Point, int, double>( );
  146. if (Height <= 35) return result;
  147. if (Width <= 20) return result;
  148. result.IsSuccess = true;
  149. result.Content2 = Height - 30;
  150. if ((Width - 40) / 2d > result.Content2)
  151. {
  152. result.Content3 = Math.Acos( 1 ) * 180 / Math.PI;
  153. }
  154. else
  155. {
  156. result.Content3 = Math.Acos( (Width - 40) / 2d / (Height - 30) ) * 180 / Math.PI;
  157. }
  158. result.Content1 = new Point( Width / 2, Height - 10 );
  159. return result;
  160. }
  161. private void ThreadPoolUpdateProgress( object obj )
  162. {
  163. try
  164. {
  165. // 开始计算更新细节
  166. int version = (int)obj;
  167. if (value_paint == value_current) return;
  168. double m_speed = Math.Abs( value_paint - value_current ) / 10;
  169. if (m_speed == 0) m_speed = 1;
  170. while (value_paint != value_current)
  171. {
  172. System.Threading.Thread.Sleep( 17 );
  173. if (version != m_version) break;
  174. hybirdLock.Enter( );
  175. double newActual = 0;
  176. if (value_paint > value_current)
  177. {
  178. double offect = value_paint - value_current;
  179. if (offect > m_speed) offect = m_speed;
  180. newActual = value_paint - offect;
  181. }
  182. else
  183. {
  184. double offect = value_current - value_paint;
  185. if (offect > m_speed) offect = m_speed;
  186. newActual = value_paint + offect;
  187. }
  188. value_paint = newActual;
  189. hybirdLock.Leave( );
  190. if (version == m_version)
  191. {
  192. if (IsHandleCreated) Invoke( m_UpdateAction );
  193. }
  194. else
  195. {
  196. break;
  197. }
  198. }
  199. }
  200. catch (Exception ex)
  201. {
  202. // BasicFramework.SoftBasic.ShowExceptionMessage(ex);
  203. }
  204. }
  205. #region Private Member
  206. private Color color_gauge_border = Color.DimGray;
  207. private Pen pen_gauge_border = null; // 绘制仪表盘的边框色
  208. private Color color_gauge_pointer = Color.Tomato;
  209. private Brush brush_gauge_pointer = null; // 绘制仪表盘的指针的颜色
  210. private double value_start = 0; // 仪表盘的初始值
  211. private double value_max = 100d; // 仪表盘的结束值
  212. private double value_current = 0d; // 仪表盘的当前值
  213. private double value_alarm_max = 80d; // 仪表盘的上限报警值
  214. private double value_alarm_min = 20d; // 仪表盘的下限报警值
  215. private Pen pen_gauge_alarm = null; // 绘制仪表盘的报警区间的虚线画笔
  216. private int m_version = 0; // 设置数据时的版本,用于更新时的版本验证
  217. private double value_paint = 0d; // 绘制图形时候的中间值
  218. private Core.SimpleHybirdLock hybirdLock; // 数据的同步锁
  219. private Action m_UpdateAction; // 更新界面的委托
  220. private Timer timer_alarm_check; // 数据处于危险区域的报警闪烁
  221. private bool alarm_check = false; // 每秒计时的报警反馈
  222. private int segment_count = 10; // 显示区域的分割片段
  223. private StringFormat centerFormat = null; // 居中显示的格式化文本
  224. private string value_unit_text = string.Empty; // 数值的单位,可以设置并显示
  225. private bool text_under_pointer = true; // 指示文本是否在指针的下面
  226. #endregion
  227. #region Public Member
  228. /// <summary>
  229. /// 获取或设置仪表盘的背景色
  230. /// </summary>
  231. [Browsable( true )]
  232. [Category( "外观" )]
  233. [Description( "获取或设置仪表盘的背景色" )]
  234. [DefaultValue( typeof( Color ), "DimGray" )]
  235. public Color GaugeBorder
  236. {
  237. get { return color_gauge_border; }
  238. set
  239. {
  240. pen_gauge_border?.Dispose( );
  241. pen_gauge_border = new Pen( value );
  242. color_gauge_border = value;
  243. Invalidate( );
  244. }
  245. }
  246. /// <summary>
  247. /// 获取或设置指针的颜色
  248. /// </summary>
  249. [Browsable( true )]
  250. [Category( "外观" )]
  251. [Description( "获取或设置仪表盘指针的颜色" )]
  252. [DefaultValue( typeof( Color ), "Tomato" )]
  253. public Color PointerColor
  254. {
  255. get
  256. {
  257. return color_gauge_pointer;
  258. }
  259. set
  260. {
  261. brush_gauge_pointer?.Dispose( );
  262. brush_gauge_pointer = new SolidBrush( value );
  263. color_gauge_pointer = value;
  264. Invalidate( );
  265. }
  266. }
  267. /// <summary>
  268. /// 获取或设置数值的起始值,默认为0
  269. /// </summary>
  270. [Browsable( true )]
  271. [Category( "外观" )]
  272. [Description( "获取或设置数值的起始值,默认为0" )]
  273. [DefaultValue( 0d )]
  274. public double ValueStart
  275. {
  276. get
  277. {
  278. return value_start;
  279. }
  280. set
  281. {
  282. value_start = value;
  283. if (value_max <= value_start)
  284. {
  285. value_max = value_start + 1;
  286. }
  287. Invalidate( );
  288. }
  289. }
  290. /// <summary>
  291. /// 获取或设置数值的最大值,默认为100
  292. /// </summary>
  293. [Browsable( true )]
  294. [Category( "外观" )]
  295. [Description( "获取或设置数值的最大值,默认为100" )]
  296. [DefaultValue( 100d )]
  297. public double ValueMax
  298. {
  299. get
  300. {
  301. return value_max;
  302. }
  303. set
  304. {
  305. value_max = value;
  306. if (value_max <= value_start)
  307. {
  308. value_max = value_start + 1;
  309. }
  310. Invalidate( );
  311. }
  312. }
  313. /// <summary>
  314. /// 获取或设置数值的当前值,应该处于最小值和最大值之间
  315. /// </summary>
  316. [Browsable( true )]
  317. [Category( "外观" )]
  318. [Description( "获取或设置数值的当前值,默认为0" )]
  319. [DefaultValue( 0d )]
  320. public double Value
  321. {
  322. get
  323. {
  324. return value_current;
  325. }
  326. set
  327. {
  328. if (ValueStart <= value && value <= ValueMax)
  329. {
  330. if (value != value_current)
  331. {
  332. value_current = value;
  333. int version = System.Threading.Interlocked.Increment( ref m_version );
  334. System.Threading.ThreadPool.QueueUserWorkItem( new System.Threading.WaitCallback(
  335. ThreadPoolUpdateProgress ), version );
  336. }
  337. }
  338. }
  339. }
  340. /// <summary>
  341. /// 获取或设置数值的上限报警值,设置为超过最大值则无上限报警
  342. /// </summary>
  343. [Browsable( true )]
  344. [Category( "外观" )]
  345. [Description( "获取或设置数值的上限报警值,设置为超过最大值则无上限报警,默认为80" )]
  346. [DefaultValue( 80d )]
  347. public double ValueAlarmMax
  348. {
  349. get
  350. {
  351. return value_alarm_max;
  352. }
  353. set
  354. {
  355. if (ValueStart <= value)
  356. {
  357. value_alarm_max = value;
  358. Invalidate( );
  359. }
  360. }
  361. }
  362. /// <summary>
  363. /// 获取或设置数值的下限报警值,设置为超过最大值则无上限报警
  364. /// </summary>
  365. [Browsable( true )]
  366. [Category( "外观" )]
  367. [Description( "获取或设置数值的下限报警值,设置为小于最小值则无下限报警,默认为20" )]
  368. [DefaultValue( 20d )]
  369. public double ValueAlarmMin
  370. {
  371. get
  372. {
  373. return value_alarm_min;
  374. }
  375. set
  376. {
  377. if (value <= ValueMax)
  378. {
  379. value_alarm_min = value;
  380. Invalidate( );
  381. }
  382. }
  383. }
  384. /// <summary>
  385. /// 获取或设置仪表盘的分割段数,最小为2,最大1000
  386. /// </summary>
  387. [Browsable( true )]
  388. [Category( "外观" )]
  389. [Description( "获取或设置仪表盘的分割段数,最小为2,最大1000" )]
  390. [DefaultValue( 10 )]
  391. public int SegmentCount
  392. {
  393. get
  394. {
  395. return segment_count;
  396. }
  397. set
  398. {
  399. if (value > 1 && value < 1000)
  400. {
  401. segment_count = value;
  402. }
  403. }
  404. }
  405. /// <summary>
  406. /// 获取或设置仪表盘的单位描述文本
  407. /// </summary>
  408. [Browsable( true )]
  409. [Category( "外观" )]
  410. [Description( "获取或设置仪表盘的单位描述文本" )]
  411. [DefaultValue( "" )]
  412. public string UnitText
  413. {
  414. get
  415. {
  416. return value_unit_text;
  417. }
  418. set
  419. {
  420. value_unit_text = value;
  421. Invalidate( );
  422. }
  423. }
  424. /// <summary>
  425. /// 获取或设置文本是否是指针的下面
  426. /// </summary>
  427. [Browsable( true )]
  428. [Category( "外观" )]
  429. [Description( "获取或设置文本是否是指针的下面" )]
  430. [DefaultValue( true )]
  431. public bool IsTextUnderPointer
  432. {
  433. get
  434. {
  435. return text_under_pointer;
  436. }
  437. set
  438. {
  439. text_under_pointer = value;
  440. Invalidate( );
  441. }
  442. }
  443. #endregion
  444. }
  445. }