diff --git a/Nuclex.Windows.Forms.csproj b/Nuclex.Windows.Forms.csproj index 15c6756..cfc1cec 100644 --- a/Nuclex.Windows.Forms.csproj +++ b/Nuclex.Windows.Forms.csproj @@ -1,4 +1,4 @@ - + Debug AnyCPU @@ -53,6 +53,10 @@ Documents\Nuclex.Windows.Forms.xml + + False + ..\References\nunit\net-2.0\framework\nunit.framework.dll + @@ -72,12 +76,18 @@ AsyncProgressBar.cs + + AsyncProgressBar.cs + Component ContainerListView.cs + + ContainerListView.cs + diff --git a/Source/AsyncProgressBar/AsyncProgressBar.Test.cs b/Source/AsyncProgressBar/AsyncProgressBar.Test.cs new file mode 100644 index 0000000..3f1affa --- /dev/null +++ b/Source/AsyncProgressBar/AsyncProgressBar.Test.cs @@ -0,0 +1,71 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if UNITTEST + +using System; +using System.IO; +using System.Windows.Forms; + +using NUnit.Framework; + +using Nuclex.Support; + +namespace Nuclex.Windows.Forms { + + /// Unit Test for the asynchronously updating progress bar + [TestFixture] + public class AsyncProgressBarTest { + + /// + /// Verifies that asynchronous progress assignment is working + /// + [Test] + public void TestProgressAssignment() { + using(AsyncProgressBar progressBar = new AsyncProgressBar()) { + + // Let the control create its window handle + progressBar.CreateControl(); + progressBar.Minimum = 0; + progressBar.Maximum = 100; + + Assert.AreEqual(0, progressBar.Value); + + // Assign the new value. This will be done asynchronously, so we call + // Application.DoEvents() to execute the message pump once, guaranteeing + // that the call will have been executed after Application.DoEvents() returns. + progressBar.AsyncSetValue(0.33f); + Application.DoEvents(); + + Assert.AreEqual(33, progressBar.Value); + + progressBar.AsyncSetValue(0.66f); + Application.DoEvents(); + + Assert.AreEqual(66, progressBar.Value); + + } + } + + } + +} // namespace Nuclex.Windows.Forms + +#endif // UNITTEST diff --git a/Source/AsyncProgressBar/AsyncProgressBar.cs b/Source/AsyncProgressBar/AsyncProgressBar.cs index 3990a11..33d8989 100644 --- a/Source/AsyncProgressBar/AsyncProgressBar.cs +++ b/Source/AsyncProgressBar/AsyncProgressBar.cs @@ -38,16 +38,16 @@ namespace Nuclex.Windows.Forms { // whenever I see it :) Interlocked.Exchange(ref this.newProgress, -1.0f); } - + /// Called when the progress bar is being disposed /// Progress bar that is being disposed /// Not used private void progressBarDisposed(object sender, EventArgs arguments) { // CHECK: This method is only called on an explicit Dispose() of the control. - // Microsoft officially states that it's allowed to call Control.BeginInvoke() - // without calling Control.EndInvoke(), so this code is quite correct, - // but is it also clean? :> + // It is legal to call Control.BeginInvoke() without calling Control.EndInvoke(), + // so the code is quite correct even if no Dispose() occurs, but is it also clean? + // http://www.interact-sw.co.uk/iangblog/2005/05/16/endinvokerequired // Since this has to occur in the UI thread, there's no way that updateProgress() // could be executing just now. But the final call to updateProgress() will not diff --git a/Source/ContainerListView/ContainerListView.Test.cs b/Source/ContainerListView/ContainerListView.Test.cs new file mode 100644 index 0000000..c48c1ac --- /dev/null +++ b/Source/ContainerListView/ContainerListView.Test.cs @@ -0,0 +1,83 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +#if UNITTEST + +using System; +using System.IO; +using System.Windows.Forms; + +using NUnit.Framework; + +using Nuclex.Support; + +namespace Nuclex.Windows.Forms { + + /// Unit Test for the control container list view + [TestFixture] + public class ContainerListViewTest { + + /// + /// Verifies that the asynchronous progress bar's constructor is working + /// + [Test] + public void TestConstructor() { + using(ContainerListView listView = new ContainerListView()) { + + // Let the control create its window handle + listView.CreateControl(); + listView.Columns.Add("Numeric"); + listView.Columns.Add("Spelled"); + listView.Columns.Add("Nonsense"); + + addRow(listView, "1", "One"); + addRow(listView, "2", "Two"); + addRow(listView, "3", "Three"); + + using(CheckBox checkBox = new CheckBox()) { + listView.EmbeddedControls.Add(new ListViewEmbeddedControl(checkBox, 2, 0)); + listView.EmbeddedControls.Clear(); + + listView.Refresh(); + + ListViewEmbeddedControl embeddedControl = new ListViewEmbeddedControl( + checkBox, 2, 0 + ); + listView.EmbeddedControls.Add(embeddedControl); + listView.EmbeddedControls.Remove(embeddedControl); + + listView.Refresh(); + } + + } + } + + /// Adds a row to a control container list view + /// List view control the row will be added to + /// Values that will appear in the individual columns + private void addRow(ContainerListView listView, params string[] columns) { + listView.Items.Add(new ListViewItem(columns)); + } + + } + +} // namespace Nuclex.Windows.Forms + +#endif // UNITTEST diff --git a/Source/ContainerListView/ContainerListView.cs b/Source/ContainerListView/ContainerListView.cs index 1994e8a..faaab95 100644 --- a/Source/ContainerListView/ContainerListView.cs +++ b/Source/ContainerListView/ContainerListView.cs @@ -33,15 +33,29 @@ namespace Nuclex.Windows.Forms { /// ListView allowing for other controls to be embedded in its cells /// - /// There basically were two possible design choices: Provide a specialized - /// ListViewSubItem that carries a Control instead of a string or manage the - /// embedded controls seperate of the ListView's items. The first option - /// would require a complete rewrite of the ListViewItem class and its related - /// support classes, all of which are surprisingly large and complex. Thus, - /// I chose the less clean but more doable latter option. + /// + /// There basically were two possible design choices: Provide a specialized + /// ListViewSubItem that carries a Control instead of a string or manage the + /// embedded controls seperate of the ListView's items. + /// + /// + /// The first option requires a complete rewrite of the ListViewItem class + /// and its related support classes, all of which are surprisingly large and + /// complex. Thus, I chose the less clean but more doable latter option. + /// + /// + /// This control is useful for simple item lists where you want to provide + /// a combobox, checkbox or other control to the user for a certain column. + /// It will not perform well for lists with hundreds of items since it + /// requires a control to be created per row and management of the embedded + /// controls is designed for limited usage. + /// /// public partial class ContainerListView : System.Windows.Forms.ListView { + /// Message sent to a control to let it paint itself + private const int WM_PAINT = 0x000F; + /// Initializes a new ContainerListView public ContainerListView() { this.embeddedControlClickedDelegate = new EventHandler(embeddedControlClicked); @@ -57,7 +71,86 @@ namespace Nuclex.Windows.Forms { InitializeComponent(); base.View = View.Details; - base.AllowColumnReorder = false; + + this.columnHeaderHeight = Font.Height; + } + + /// Controls being embedded in the ListView + public ICollection EmbeddedControls { + get { return this.embeddedControls; } + } + + /// Updates the controls embeded into the list view + public void UpdateEmbeddedControls() { + if(View != View.Details) { + for(int index = 0; index < this.embeddedControls.Count; ++index) { + this.embeddedControls[index].Control.Visible = false; + } + } else { + for(int index = 0; index < this.embeddedControls.Count; ++index) { + ListViewEmbeddedControl embeddedControl = this.embeddedControls[index]; + + Rectangle cellBounds = this.GetSubItemBounds( + Items[embeddedControl.Row], embeddedControl.Column + ); + + bool intersectsColumnHeader = + (base.HeaderStyle != ColumnHeaderStyle.None) && + (cellBounds.Top < base.Font.Height); + + embeddedControl.Control.Visible = !intersectsColumnHeader; + embeddedControl.Control.Bounds = cellBounds; + } + } + } + + /// Calculates the boundaries of a cell in the list view + /// Item in the list view from which to calculate the cell + /// Index der cell whose boundaries to calculate + /// The boundaries of the specified list view cell + /// + /// When the specified sub item index is not in the range of valid sub items + /// + protected Rectangle GetSubItemBounds(ListViewItem item, int subItem) { + int[] order = GetColumnOrder(); + if(order == null) // No Columns + return Rectangle.Empty; + + if(subItem >= order.Length) + throw new IndexOutOfRangeException("SubItem " + subItem + " out of range"); + + // Determine the border of the entire ListViewItem, including all sub items + Rectangle itemBounds = item.GetBounds(ItemBoundsPortion.Entire); + int subItemX = itemBounds.Left; + + // Find the horizontal position of the sub item. Because the column order can vary, + // we need to use Columns[order[i]] instead of simply doing Columns[i] here! + ColumnHeader columnHeader; + int i; + for(i = 0; i < order.Length; ++i) { + columnHeader = this.Columns[order[i]]; + if(columnHeader.Index == subItem) + break; + + subItemX += columnHeader.Width; + } + + return new Rectangle( + subItemX, itemBounds.Top, this.Columns[order[i]].Width, itemBounds.Height + ); + } + + /// Responds to window messages sent by the operating system + /// Window message that will be processed + protected override void WndProc(ref Message message) { + switch(message.Msg) { + case WM_PAINT: { + UpdateEmbeddedControls(); + break; + } + } + + base.WndProc(ref message); } /// Called when the list of embedded controls has been cleared @@ -82,8 +175,7 @@ namespace Nuclex.Windows.Forms { /// Event arguments providing a reference to the removed control /// private void embeddedControlAdded( - object sender, - ItemEventArgs arguments + object sender, ItemEventArgs arguments ) { arguments.Item.Control.Click += this.embeddedControlClickedDelegate; this.Controls.Add(arguments.Item.Control); @@ -95,8 +187,7 @@ namespace Nuclex.Windows.Forms { /// Event arguments providing a reference to the added control /// private void embeddedControlRemoved( - object sender, - ItemEventArgs arguments + object sender, ItemEventArgs arguments ) { if(this.Controls.Contains(arguments.Item.Control)) { arguments.Item.Control.Click -= this.embeddedControlClickedDelegate; @@ -125,44 +216,6 @@ namespace Nuclex.Windows.Forms { } } - /// Calculates the boundaries of a cell in the list view - /// Item in the list view from which to calculate the cell - /// Index der cell whose boundaries to calculate - /// The boundaries of the specified list view cell - /// - /// When the specified sub item index is not in the range of valid sub items - /// - protected Rectangle GetSubItemBounds(ListViewItem item, int subItem) { - - int[] order = GetColumnOrder(); - if(order == null) // No Columns - return Rectangle.Empty; - - if(subItem >= order.Length) - throw new IndexOutOfRangeException("SubItem " + subItem + " out of range"); - - // Determine the border of the entire ListViewItem, including all sub items - Rectangle itemBounds = item.GetBounds(ItemBoundsPortion.Entire); - int subItemX = itemBounds.Left; - - // Find the horizontal position of the sub item. Because the column order can vary, - // we need to use Columns[order[i]] instead of simply doing Columns[i] here! - ColumnHeader columnHeader; - int i; - for(i = 0; i < order.Length; ++i) { - columnHeader = this.Columns[order[i]]; - if(columnHeader.Index == subItem) - break; - - subItemX += columnHeader.Width; - } - - return new Rectangle( - subItemX, itemBounds.Top, this.Columns[order[i]].Width, itemBounds.Height - ); - - } - /// Obtains the current column order of the list /// An array indicating the order of the list's columns private int[] GetColumnOrder() { @@ -174,6 +227,8 @@ namespace Nuclex.Windows.Forms { return order; } + /// Height of the list view's column header + private int columnHeaderHeight; /// Event handler for when embedded controls are clicked on private EventHandler embeddedControlClickedDelegate; /// Controls being embedded in this ListView diff --git a/Source/ProgressReporter/ProgressReporterForm.cs b/Source/ProgressReporter/ProgressReporterForm.cs index f4de20c..d67980f 100644 --- a/Source/ProgressReporter/ProgressReporterForm.cs +++ b/Source/ProgressReporter/ProgressReporterForm.cs @@ -17,18 +17,24 @@ namespace Nuclex.Windows.Forms { /// window during a modal asynchronous processes. /// /// - /// class Test : Nuclex.Support.Scheduling.ThreadOperation { - /// static void Main() { - /// Test myTest = new Test(); - /// myTest.Begin(); - /// Nuclex.Windows.Forms.ProgressReporterForm.Track(myTest); - /// myTest.End(); + /// + /// class Test : Nuclex.Support.Scheduling.ThreadOperation { + /// + /// static void Main() { + /// Test myTest = new Test(); + /// myTest.Begin(); + /// Nuclex.Windows.Forms.ProgressReporterForm.Track(myTest); + /// myTest.End(); + /// } + /// + /// protected override void Execute() { + /// for(int i = 0; i < 10000000; ++i) { + /// OnAsyncProgressUpdated((float)i / 10000000.0f); + /// } + /// } + /// /// } - /// protected override void Execute() { - /// for(int i = 0; i < 10000000; ++i) - /// OnAsyncProgressUpdated((float)i / 10000000.0f); - /// } - /// } + /// /// public partial class ProgressReporterForm : Form {