1616// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1717// DEALINGS IN THE SOFTWARE.
1818
19+ using System ;
20+ using System . ComponentModel ;
1921using System . Composition ;
2022using System . Linq ;
2123using System . Windows ;
2224using System . Windows . Controls ;
2325using System . Windows . Controls . Primitives ;
2426using System . Windows . Data ;
2527using System . Windows . Input ;
28+ using System . Windows . Media ;
2629using System . Windows . Threading ;
2730
2831using ICSharpCode . ILSpy . Themes ;
32+ using ICSharpCode . ILSpyX . TreeView ;
2933
3034using TomsToolbox . Composition ;
3135
@@ -85,7 +89,8 @@ static void InitToolbar(ToolBar toolBar, IExportProvider exportProvider)
8589 }
8690 }
8791
88- static Button CreateToolbarItem ( IExport < ICommand , IToolbarCommandMetadata > commandExport )
92+
93+ static UIElement CreateToolbarItem ( IExport < ICommand , IToolbarCommandMetadata > commandExport )
8994 {
9095 var command = commandExport . Value ;
9196
@@ -108,9 +113,106 @@ static Button CreateToolbarItem(IExport<ICommand, IToolbarCommandMetadata> comma
108113 parameterBinding . ParameterBinding ) ;
109114 }
110115
116+ if ( command is IProvideParameterList parameterList )
117+ {
118+ toolbarItem . Margin = new Thickness ( 2 , 0 , 0 , 0 ) ;
119+
120+ var dropDownPanel = new StackPanel { Orientation = Orientation . Horizontal } ;
121+
122+ var dropDownToggle = new ToggleButton {
123+ Style = ThemeManager . Current . CreateToolBarToggleButtonStyle ( ) ,
124+ Content = "▾" ,
125+ Padding = new Thickness ( 0 ) ,
126+ MinWidth = 0 ,
127+ Margin = new Thickness ( 0 , 0 , 2 , 0 )
128+ } ;
129+
130+ var contextMenu = new ContextMenu {
131+ PlacementTarget = dropDownPanel ,
132+ Tag = command
133+ } ;
134+
135+ ContextMenuService . SetPlacement ( toolbarItem , PlacementMode . Bottom ) ;
136+ toolbarItem . ContextMenu = contextMenu ;
137+ toolbarItem . ContextMenuOpening += ( _ , _ ) =>
138+ PrepareParameterList ( contextMenu ) ;
139+ dropDownToggle . Checked += ( _ , _ ) => {
140+ PrepareParameterList ( contextMenu ) ;
141+ contextMenu . Placement = PlacementMode . Bottom ;
142+ contextMenu . SetCurrentValue ( ContextMenu . IsOpenProperty , true ) ;
143+ } ;
144+
145+ BindingOperations . SetBinding ( dropDownToggle , ToggleButton . IsCheckedProperty ,
146+ new Binding ( nameof ( contextMenu . IsOpen ) ) { Source = contextMenu } ) ;
147+
148+ BindingOperations . SetBinding ( dropDownToggle , IsEnabledProperty ,
149+ new Binding ( nameof ( IsEnabled ) ) { Source = toolbarItem } ) ;
150+
151+ // When the toggle button is checked, clicking it to uncheck will dismiss the menu first
152+ // which unchecks the toggle button via binding above and the click is used to open it again.
153+ // This is a workaround to ignore the click to uncheck the already unchecked toggle button.
154+ // We have to ensure the dismissing click is on the toggle button, otherwise the flag
155+ // will not get cleared and menu will not open next time.
156+ Mouse . AddPreviewMouseDownOutsideCapturedElementHandler ( contextMenu , ( _ , e ) => {
157+ var point = e . GetPosition ( dropDownToggle ) ;
158+ dropDownToggle . Tag = dropDownToggle . InputHitTest ( point ) ;
159+ } ) ;
160+ dropDownToggle . PreviewMouseLeftButtonDown += ( _ , e ) => {
161+ e . Handled = dropDownToggle . Tag != null ;
162+ dropDownToggle . Tag = null ;
163+ } ;
164+
165+ dropDownPanel . Children . Add ( toolbarItem ) ;
166+ dropDownPanel . Children . Add ( dropDownToggle ) ;
167+ return dropDownPanel ;
168+ }
169+
111170 return toolbarItem ;
112171 }
113172
173+ static void PrepareParameterList ( ContextMenu menu )
174+ {
175+ const int maximumParameterListCount = 20 ;
176+
177+ var command = ( ICommand ) menu . Tag ;
178+ var parameterList = ( IProvideParameterList ) command ;
179+
180+ menu . Items . Clear ( ) ;
181+ foreach ( var parameter in parameterList . ParameterList )
182+ {
183+ MenuItem parameterItem = new MenuItem ( ) ;
184+ parameterItem . Command = CommandWrapper . Unwrap ( command ) ;
185+ parameterItem . CommandParameter = parameter ;
186+ parameterItem . CommandTarget = menu . PlacementTarget ;
187+ parameterItem . InputGestureText = " " ;
188+
189+ var headerPresenter = new ContentPresenter { RecognizesAccessKey = false } ;
190+ parameterItem . Header = headerPresenter ;
191+
192+ var header = parameterList . GetParameterText ( parameter ) ;
193+ switch ( header )
194+ {
195+ case SharpTreeNode node :
196+ headerPresenter . Content = node . NavigationText ;
197+ if ( node . Icon is ImageSource icon )
198+ parameterItem . Icon = new Image {
199+ Width = 16 ,
200+ Height = 16 ,
201+ Source = icon
202+ } ;
203+ break ;
204+
205+ default :
206+ headerPresenter . Content = header ;
207+ break ;
208+ }
209+
210+ menu . Items . Add ( parameterItem ) ;
211+ if ( menu . Items . Count >= maximumParameterListCount )
212+ break ;
213+ }
214+ }
215+
114216 void MainWindow_KeyDown ( object sender , KeyEventArgs e )
115217 {
116218 if ( e . Handled || e . KeyboardDevice . Modifiers != ModifierKeys . Alt || e . Key != Key . System )
0 commit comments