@@ -12,6 +12,9 @@ internal sealed class MainForm : Form
1212{
1313 private const string ProgId = "MapWinGIS6.Control.6" ;
1414 private const int ColorWindow = 5 ;
15+ private const int HResultFail = unchecked ( ( int ) 0x80004005 ) ;
16+ private const int HResultInvalidArgument = unchecked ( ( int ) 0x80070057 ) ;
17+ private const int HResultDispatchException = unchecked ( ( int ) 0x80020009 ) ;
1518 private const double ZoomStep = 1.5 ;
1619 private static readonly Color MapFillColor = Color . FromArgb ( 61 , 122 , 169 ) ;
1720
@@ -240,8 +243,9 @@ private async Task RunAutomatedAsync()
240243 RefreshStatus ( ) ;
241244 } ) ;
242245 RecordCheck ( "Load vector layer" , LoadTestLayer ) ;
246+ RecordCheck ( "Invalid dataset handling" , VerifyInvalidDatasetHandling ) ;
243247 await RecordCheckAsync ( "GDAL COM event sinks" , VerifyGdalEventSinksAsync ) ;
244- RecordCheck ( "CPU backend preference and status " , VerifyCpuBackendSelection ) ;
248+ RecordCheck ( "Rendering backends " , VerifyRenderingBackendsWhereSupported ) ;
245249
246250 RecordCheck ( "Resize" , VerifyResize ) ;
247251 await RecordCheckAsync ( "Repaint" , VerifyRepaintAsync ) ;
@@ -254,7 +258,16 @@ private async Task RunAutomatedAsync()
254258 } ) ;
255259 RecordCheck ( "Reload vector layer" , LoadTestLayer ) ;
256260 await RecordCheckAsync ( "Recreated repaint" , VerifyRepaintAsync ) ;
257- RecordCheck ( "Layer loading, navigation and clear" , VerifyLayerOperations ) ;
261+ RecordCheck ( "Multiple datasets and stable layer IDs" , VerifyMultipleDatasetLoading ) ;
262+ RecordCheck ( "Invalid layer ID handling" , VerifyInvalidLayerIdHandling ) ;
263+ RecordCheck ( "Zoom to individual layers" , VerifyZoomToLayers ) ;
264+ RecordCheck ( "Zoom in and zoom out" , VerifyZoomInAndOut ) ;
265+ RecordCheck ( "Pan" , VerifyPan ) ;
266+ RecordCheck ( "ZoomAt" , VerifyZoomAt ) ;
267+ RecordCheck ( "Zoom to map" , VerifyFitToMap ) ;
268+ RecordCheck ( "Navigation after resizing" , VerifyNavigationAfterResize ) ;
269+ RecordCheck ( "Clear and reload layers" , VerifyClearAndReload ) ;
270+ RecordCheck ( "Navigation on an empty map" , VerifyEmptyMapNavigation ) ;
258271 RecordCheck ( "Interactive load rendering status" , VerifyInteractiveLoadStatus ) ;
259272 RecordCheck ( "Final dispose" , VerifyDispose ) ;
260273
@@ -609,7 +622,32 @@ private void ClearLoadedLayers()
609622 RefreshStatus ( ) ;
610623 }
611624
612- private void VerifyLayerOperations ( )
625+ private void VerifyInvalidDatasetHandling ( )
626+ {
627+ dynamic automation = GetAutomation ( ) ;
628+ int previousLayerCount = ( int ) automation . LayerCount ;
629+ long previousFeatureCount = ( long ) automation . FeatureCount ;
630+ long previousLayerId = ( long ) automation . GetLayerId ( 0 ) ;
631+ string missingPath = Path . Combine ( _testDatasetDirectory , "missing.geojson" ) ;
632+
633+ ExpectComFailure (
634+ ( ) =>
635+ {
636+ _ = automation . LoadVectorDatasetEx ( missingPath ) ;
637+ } ,
638+ HResultFail ,
639+ "loading a missing dataset" ) ;
640+
641+ if ( ( int ) automation . LayerCount != previousLayerCount ||
642+ ( long ) automation . FeatureCount != previousFeatureCount ||
643+ ( long ) automation . GetLayerId ( 0 ) != previousLayerId )
644+ {
645+ throw new InvalidOperationException (
646+ "A failed dataset load changed the existing layers or feature count." ) ;
647+ }
648+ }
649+
650+ private void VerifyMultipleDatasetLoading ( )
613651 {
614652 ClearLoadedLayers ( ) ;
615653 List < string > warnings = [ ] ;
@@ -620,36 +658,218 @@ private void VerifyLayerOperations()
620658 throw new InvalidOperationException ( string . Join ( Environment . NewLine , warnings ) ) ;
621659 }
622660
623- dynamic automation = _host ! . AutomationObject ! ;
661+ dynamic automation = GetAutomation ( ) ;
624662 if ( first . Id <= 0 || second . Id <= 0 || first . Id == second . Id ||
625663 first . FeatureCount != 2 || second . FeatureCount != 2 ||
664+ ( int ) automation . LayerCount != 2 || ( long ) automation . FeatureCount != 4 ||
626665 ( long ) automation . GetLayerId ( 0 ) != first . Id ||
627666 ( long ) automation . GetLayerId ( 1 ) != second . Id ||
628667 _layerList . Items . Count != 2 )
629668 {
630669 throw new InvalidOperationException ( "The two loaded layers have inconsistent IDs or feature counts." ) ;
631670 }
671+ }
632672
633- automation . ZoomToLayer ( first . Id ) ;
634- automation . ZoomToLayer ( second . Id ) ;
635- automation . Pan ( 1.0 , - 1.0 ) ;
636- RepaintHost ( ) ;
637- if ( ! ( bool ) automation . LayerLoaded || ( int ) automation . LayerCount != 2 ||
638- ( long ) automation . FeatureCount != 4 )
673+ private void VerifyInvalidLayerIdHandling ( )
674+ {
675+ dynamic automation = GetAutomation ( ) ;
676+ ExpectComFailure (
677+ ( ) => automation . ZoomToLayer ( long . MaxValue ) ,
678+ HResultInvalidArgument ,
679+ "zooming to an unknown layer ID" ,
680+ HResultDispatchException ) ;
681+ VerifyLayerTotals ( 2 , 4 ) ;
682+ }
683+
684+ private void VerifyZoomToLayers ( )
685+ {
686+ dynamic automation = GetAutomation ( ) ;
687+ for ( int index = 0 ; index < _layerList . Items . Count ; index ++ )
688+ {
689+ LoadedLayer layer = GetLoadedLayer ( index ) ;
690+ automation . ZoomToLayer ( layer . Id ) ;
691+ RepaintHost ( ) ;
692+ }
693+
694+ VerifyLayerTotals ( 2 , 4 ) ;
695+ }
696+
697+ private void VerifyZoomInAndOut ( )
698+ {
699+ VerifyNavigation ( automation =>
700+ {
701+ automation . ZoomIn ( ZoomStep ) ;
702+ automation . ZoomOut ( ZoomStep ) ;
703+ } ) ;
704+ }
705+
706+ private void VerifyPan ( )
707+ {
708+ VerifyNavigation ( automation => automation . Pan ( 1.0 , - 1.0 ) ) ;
709+ }
710+
711+ private void VerifyZoomAt ( )
712+ {
713+ ActiveXHostControl host = _host ??
714+ throw new InvalidOperationException ( "The ActiveX control has not been created." ) ;
715+ VerifyNavigation ( automation =>
716+ {
717+ automation . ZoomAt ( host . Width / 2.0 , host . Height / 2.0 , ZoomStep ) ;
718+ } ) ;
719+ }
720+
721+ private void VerifyFitToMap ( )
722+ {
723+ VerifyNavigation ( automation => automation . FitToMap ( ) ) ;
724+ }
725+
726+ private void VerifyNavigationAfterResize ( )
727+ {
728+ if ( _host is null )
729+ {
730+ throw new InvalidOperationException ( "The ActiveX control has not been created." ) ;
731+ }
732+
733+ ActiveXHostControl host = _host ;
734+ ClientSize = new Size ( 900 , 600 ) ;
735+ PerformLayout ( ) ;
736+ Application . DoEvents ( ) ;
737+ if ( host . Bounds != _surfacePanel . ClientRectangle )
738+ {
739+ throw new InvalidOperationException (
740+ $ "The hosted surface did not follow the resized panel. Control={ host . Bounds } , panel={ _surfacePanel . ClientRectangle } .") ;
741+ }
742+
743+ VerifyNavigation ( automation =>
744+ {
745+ automation . ZoomIn ( ZoomStep ) ;
746+ automation . Pan ( 1.0 , - 1.0 ) ;
747+ automation . ZoomAt ( host . Width / 2.0 , host . Height / 2.0 , ZoomStep ) ;
748+ automation . FitToMap ( ) ;
749+ } ) ;
750+ }
751+
752+ private void VerifyClearAndReload ( )
753+ {
754+ long previousLayerId = GetLoadedLayer ( _layerList . Items . Count - 1 ) . Id ;
755+ ClearLoadedLayers ( ) ;
756+ List < string > warnings = [ ] ;
757+ LoadedLayer reloadedLayer = AddVectorDataset ( TestDatasetPath , warnings ) ;
758+ if ( warnings . Count > 0 )
759+ {
760+ throw new InvalidOperationException ( string . Join ( Environment . NewLine , warnings ) ) ;
761+ }
762+
763+ if ( reloadedLayer . Id <= previousLayerId ||
764+ reloadedLayer . FeatureCount != 2 ||
765+ ( int ) GetAutomation ( ) . LayerCount != 1 ||
766+ ( long ) GetAutomation ( ) . FeatureCount != 2 ||
767+ _layerList . Items . Count != 1 )
639768 {
640- throw new InvalidOperationException ( "Navigation changed the loaded layer or feature counts." ) ;
769+ throw new InvalidOperationException (
770+ "Reloading after clearing did not create a new stable layer with the expected feature count." ) ;
641771 }
772+ }
642773
774+ private void VerifyEmptyMapNavigation ( )
775+ {
643776 ClearLoadedLayers ( ) ;
644- if ( _layerList . Items . Count != 0 )
777+ VerifyNavigation (
778+ automation =>
779+ {
780+ automation . FitToMap ( ) ;
781+ automation . ZoomIn ( ZoomStep ) ;
782+ automation . ZoomOut ( ZoomStep ) ;
783+ automation . Pan ( 1.0 , - 1.0 ) ;
784+ automation . ZoomAt ( 0.0 , 0.0 , ZoomStep ) ;
785+ } ,
786+ expectedLayerCount : 0 ,
787+ expectedFeatureCount : 0 ) ;
788+ }
789+
790+ private dynamic GetAutomation ( )
791+ {
792+ if ( _host ? . AutomationObject is not object automationObject )
793+ {
794+ throw new InvalidOperationException ( "The ActiveX automation object is unavailable." ) ;
795+ }
796+
797+ return automationObject ;
798+ }
799+
800+ private LoadedLayer GetLoadedLayer ( int index )
801+ {
802+ if ( index < 0 || index >= _layerList . Items . Count ||
803+ _layerList . Items [ index ] . Tag is not LoadedLayer layer )
804+ {
805+ throw new InvalidOperationException ( $ "No loaded layer exists at index { index } .") ;
806+ }
807+
808+ return layer ;
809+ }
810+
811+ private void VerifyNavigation (
812+ Action < dynamic > operation ,
813+ int expectedLayerCount = 2 ,
814+ long expectedFeatureCount = 4 )
815+ {
816+ dynamic automation = GetAutomation ( ) ;
817+ operation ( automation ) ;
818+ RepaintHost ( ) ;
819+ RefreshStatus ( ) ;
820+ VerifyLayerTotals ( expectedLayerCount , expectedFeatureCount ) ;
821+ }
822+
823+ private void VerifyLayerTotals ( int expectedLayerCount , long expectedFeatureCount )
824+ {
825+ dynamic automation = GetAutomation ( ) ;
826+ if ( ( int ) automation . LayerCount != expectedLayerCount ||
827+ ( long ) automation . FeatureCount != expectedFeatureCount ||
828+ ( bool ) automation . LayerLoaded != ( expectedLayerCount > 0 ) )
829+ {
830+ throw new InvalidOperationException (
831+ $ "Expected { expectedLayerCount } layers and { expectedFeatureCount } features after the operation.") ;
832+ }
833+ }
834+
835+ private static void ExpectComFailure (
836+ Action operation ,
837+ int expectedHResult ,
838+ string operationName ,
839+ int ? alternateHResult = null )
840+ {
841+ try
645842 {
646- throw new InvalidOperationException ( "The layer list was not cleared." ) ;
843+ operation ( ) ;
647844 }
845+ catch ( Exception exception )
846+ {
847+ Exception failure = exception . GetBaseException ( ) ;
848+ if ( failure is not COMException and not ArgumentException )
849+ {
850+ throw new InvalidOperationException (
851+ $ "The { operationName } threw { failure . GetType ( ) . Name } : { failure . Message } ") ;
852+ }
853+
854+ if ( failure . HResult == expectedHResult ||
855+ failure . HResult == alternateHResult )
856+ {
857+ return ;
858+ }
859+
860+ throw new InvalidOperationException (
861+ $ "The { operationName } returned HRESULT 0x{ failure . HResult : x8} , expected 0x{ expectedHResult : x8} " +
862+ ( alternateHResult . HasValue ? $ " or 0x{ alternateHResult : x8} ." : "." ) +
863+ $ " COM error: { failure . Message } ") ;
864+ }
865+
866+ throw new InvalidOperationException ( $ "The { operationName } unexpectedly succeeded.") ;
648867 }
649868
650869 private void VerifyInteractiveLoadStatus ( )
651870 {
652- dynamic automation = _host ! . AutomationObject ! ;
871+ ClearLoadedLayers ( ) ;
872+ dynamic automation = GetAutomation ( ) ;
653873 string previousPreference = automation . RenderingBackendPreference ;
654874 try
655875 {
@@ -786,14 +1006,9 @@ private void VerifyCoreInitialized(string activation)
7861006 }
7871007 }
7881008
789- private void VerifyCpuBackendSelection ( )
1009+ private void VerifyRenderingBackendsWhereSupported ( )
7901010 {
791- if ( _host ? . AutomationObject is not object automationObject )
792- {
793- throw new InvalidOperationException ( "The ActiveX automation object is unavailable." ) ;
794- }
795-
796- dynamic automation = automationObject ;
1011+ dynamic automation = GetAutomation ( ) ;
7971012 string previousPreference = automation . RenderingBackendPreference ;
7981013 try
7991014 {
@@ -810,10 +1025,19 @@ private void VerifyCpuBackendSelection()
8101025 }
8111026
8121027 VerifyMapPixels ( ) ;
1028+
1029+ automation . RenderingBackendPreference = "OpenGL" ;
1030+ RepaintHost ( ) ;
1031+ string requestedBackend = automation . RenderingBackend ;
1032+ if ( requestedBackend is not "GDI" and not "OpenGL" )
1033+ {
1034+ throw new InvalidOperationException (
1035+ $ "The control reported an unknown rendering backend '{ requestedBackend } '.") ;
1036+ }
1037+ VerifyMapPixels ( ) ;
8131038 }
8141039 finally
8151040 {
816- // Later resize/repaint checks must exercise the default backend path.
8171041 automation . RenderingBackendPreference = previousPreference ;
8181042 RepaintHost ( ) ;
8191043 }
0 commit comments