I am working with a form where some ComboBoxes can be created and removed programmatically.
When they are created, some triggers which target them are created and applied to a button:
Dictionary<ComboBox, DataTrigger> triggers = new Dictionary<ComboBox, DataTrigger>();
private void CreateTrigger(ComboBox box)
{
Style s = new Style(typeof(Button), MyButton.Style);
foreach(TriggerBase aTrigger in MyButton.Style.Triggers)
s.Triggers.Add(aTrigger);
DataTrigger t = new DataTrigger
{
Binding = new Binding("SelectedItem") { Source = box },
Value = null
};
t.Setters.Add(new Setter(Button.IsEnabledProperty, false));
s.Triggers.Add(t);
triggers.Add(box, t);
MyButton.Style = s;
}
So far so good*. . . the problem is, what to do when the ComboBox is removed from the window. I need to remove the trigger from the button's Style, since I no longer want the ComboBox to influence its behavior. I tried the most obvious option:
private void RemoveTrigger(ComboBox box)
{
Style s = new Style(typeof(Button), MyButton.Style);
foreach(TriggerBase aTrigger in MyButton.Style)
if(aTrigger != triggers[box]) s.Triggers.Add(aTrigger);
triggers.Remove(box);
MyButton.Style = s;
}
But this does not seem to do the job - if the trigger is removed while it is active, then the button stays disabled.
I had assumed that the button would re-evaluate its Style whenever it is given a new one. that seems to be happening when the trigger is added, but not when it's being removed - what am I missing here?
EDIT: Changed code for adding/removing triggers as per the advice in H.B.'s comment. However, the problem in question remains.
EDIT 2: *Maybe not so far so good after all - I went on to try adding an additional ComboBox (and trigger) and discovered that adding a second trigger seems to break the first one. Using this code, only the most recently added trigger works. Should I be perhaps thinking of a FrameworkElement's triggers as a write-once collection and finding a different way to achieve this kind of behavior?
So you create a style BasedOn the style of MyButton (that is what this contructor does), then you add the trigger and change the reference of your button's style to your new style. In the removal you create a new style, again based on the style currently referenced by your button, remove a trigger from its trigger collection which will not do anything as the collection is empty and then reassign this style again.
Nope, this of course won't work.
Edit: Create a base-style as a readonly reference, then when those dynamic triggers are to be added or removed create a new style based on your reference and recreate all triggers while iterating over your trigger-collection.
It's been a long time since this question was asked, but I figured I'd at least post how I resolved the issue for the sake of sharing:
I never did find a way to remove triggers that worked reliably. So instead, I added a property to my View which indicated how all the triggers would have evaluated, if they had existed, and hooked a DataTrigger up to this property.
public bool TriggerPoseur { get; set; } // Actually notifies when it changes
In place of adding and removing triggers, created an handlers to watch the properties that the triggers would have watched:
public void ComboBoxDataContext_SelectedItemChanged(object sender, PropertyChangedEventArgs e) { //update TriggerPoseur }
This sidesteps all the hassle with creating and removing triggers. Instead there's one trigger, and adding and removing event handlers works just fine.
(Hacky, yes.)
Related
I'm working on an android app which is based on Xamarin.Forms and I've created a custom dialog that shows a TimePicker (not TimePickerDialog).
So far everything works, except the fact that I've only found one way to check if the user made a selection in the TimePicker which is TimePicker.Unfocused.
Sadly that event does not return which of the two buttons in the picker was clicked : (
I tried working around this by saving the time with which I initially created the TimePicker and used that to compare if it got changed by the user. Now I've changed it so that it conveniently saves you the need to change the default time, and noticed that this is a problem now, since I have no longer any way of knowing which button was used to close the TimePicker.
var picker = new TimePicker() {
// if 'minutes' is set, use it's value as default value for the picker, otherwise the day beginning
Time = (minutes > -1 ? new TimeSpan(0, minutes, 0) : dayBegin),
};
picker.Unfocused += (sndr, evt) => {
var tp = (TimePicker)sndr;
var changed = tp.Time != oldTime;
if (changed) {
Debug.WriteLine("unfocused: " + tp.Time);
}
else {
Debug.WriteLine("canceled?");
}
dialog.Close(cpDialog.DialogResult.Yes);
// close my own dialog that showed the picker
};
It would be nice if the event for .Unfocused += would include the closing choice aside of just IsFocused.
Or if there would be alternatively events for attaching like .ClickedCancel += and .ClickedOK +=.
The TimePicker Class lacks the event "TimeSelected" found for example in the DatePicker class ("DateSeleted")
But!
If you look up the TimePicker Class, you can see that there's some BindableProperties there.
One of them is:
public static readonly BindableProperty TimeProperty;
That means that, in your xaml file, in the TimePicker block you can bind the TimeProperty to a property in your ViewModel, for example:
<TimePicker x:Name="DeliveryTimePicker"
Time="{Binding TimeValue}">
</TimePicker>
(TimeValue is a property of type TimeSpan I created in my ViewModel)
That property will update when you click the "Ok" button in your TimePicker.
(Which I believe is what you want!)
(Further info)
You can Bind directly in your xaml to the other BindableProperties as well, the way you do that is you type their name without the "property" part TimeProperty -> Time.
You can also subscribe in your xaml to the PropertyChanged event and check if the name of the property is equal to the one you are trying to look for.
For debug purposes, create that method, put a breakpoint and look for event.PropertyName, and watch the breakpoint being hit several times, the property's name will be different when creating the TimePicker, clicking Ok, etc.
I figured out a workaround: by setting the initial time to something with seconds, like 1 second at the end, the time can be compared to the initial one.
If the seconds are still present, the user clicked cancel.
I have a combo box which I need to mirror in another tab page in a C# winforms based application.
I have perfectly working code for when you select a different item from the drop down list. Unfortunately, however, when I change the Text of a tab that has not been clicked on yet nothing actually happens.
If I first click each tab then everything works as expected.
Now I'm putting this down to some form of lack of initialisation happening first. So I've tried to select each tab in my constructor.
tabControlDataSource.SelectedIndex = 0;
tabControlDataSource.SelectedIndex = 1;
// etc
But this doesn't work.
I've also tried calling tabControlDataSource.SelectTab( 1 ) and still it doesn't work.
Does anyone know how I can force the tab to "initialise"?
Ok, typically I post the question after struggling for an hour and shortly afterwards find the solution.
TabPages are lazily initialised. So they don't fully initialise until they are made visible for the first time.
So i added this code to my constructor:
tabControlDataSource.TabPages[0].Show();
tabControlDataSource.TabPages[1].Show();
tabControlDataSource.TabPages[2].Show();
but this didn't work :(
It occurred to me, however, that the constructor might not be the best place. So I created an event handler for Shown as follows:
private void MainForm_Shown( object sender, EventArgs e )
{
tabControlDataSource.TabPages[0].Show();
tabControlDataSource.TabPages[1].Show();
tabControlDataSource.TabPages[2].Show();
}
And now everything is working!
Perhaps you could also use sort of a "lazy" synchronization (initialization) in this case. Quick robust ideas: polling timer to update content (which will update it once you see tab page), no dependses within second tab (no Changed events for combobox to update second tab content, use original combobox from first tab or rather have it's content underlying in accessable for both comboboxes class, etc), "reinitialization" when tab become visible (at which moment you also init your second combobox)...
Can't be a hour, no way =D
All menus/contextmenus/toolbars I use in wpf are declared in ViewModel code pretty much like this:
MenuService.Add( new MenuItem()
{
Header = "DoStuff",
Command = new relayCommand( DoStuff, () => CanDoStuffExecute() )
// some more properties like parent item/image/...
} );
The MenuService provides a single binding point which is a hierarchical list of MenuItem and gets bound to the actual Menu's ItemsSource in xaml.
This works very well and now I'd like to add keyboard shortcuts in the same convenient way.
Ideally MenuItem would get a property of type System.Windows.Input.KeyGesture so I can simply write
Shortcut = new KeyGesture( Key.D, ModifierKeys.Control )
which would result in the Command of the item being called upon hitting Ctrl+D in the window that owns the menu, and which would also lead to automatically display "Ctrl+D" in the menu.
However I'm lost here: I wanted to set the MenuItem.InputBindings collection via databinding but it is get-only. How can I get items into it anyway? Or is there an MVVM framework that already supports something like this? Most q&a I found on keyboard shortcuts are all about setting the shortcuts through xaml, which is of no help.
Update
Searching for 'relaycommand vs routeduicommand and 'relaycommand keygesture' etc did reveal enough information to come up with a working though hacky solution. There are definitely other and better ways out there, but at the moment this is ultra low priority for me and does the job perfectly. I added two properties to the MenuItem class like this:
//Upon setting a Gesture, the original command is replaced with a RoutedCommand
//since that automatically gives us proper display of the keyboard shortcut.
//The RoutedCommand simply calls back into the original Command.
//It also sets the CommandBinding property, which still has to be added to the
//CommandBindingCollection of either the bound control or one of it ancestors
public InputGesture Gesture
{
set
{
var origCmd = Command;
if( origCmd == null )
return;
var routedCmd = new RoutedCommand( Header,
typeof( System.Windows.Controls.MenuItem ),
new InputGestureCollection { value } );
CommandBinding = new CommandBinding( routedCmd,
( sender, args ) => origCmd.Execute( args.Parameter ),
( sender, args ) => { args.CanExecute = origCmd.CanExecute( args.Parameter ); } );
Command = routedCmd;
}
}
//CommandBinding created when setting Gesture
public CommandBinding CommandBinding { get; private set; }
So this gives the functionality I asked for originally (ie adding keyboard shortcuts in code where they are easily configurable etc). All that is left is to register the commandbindings. At the moment this is done simply by adding all of them to Application.Current.MainWindow.CommandBindings.
This doesn't actually qualify as an 'answer' (I'm not able to add a comment evidently) - but I'd suggest that what you're doing, is not the intended method in WPF. You're doing this the Windows Forms way (and as in many other toolkits) - defining your UX in code. You got as far as you did, but now you've run into a brick wall: the key gestures are purely UX, definitely not to be specified in code-behind. The appearance (as a function of the view-model), and the user's interaction with it (ways of making a given command happen) are for the XAML definition.
Property values, and Commands are for your view-model, so that you can reuse this view-model for other views, and also easily create unit-tests for it. How would implementing your keyboard shortcuts in the view-model help the testability? And for use in other views, one could argue that the actual shortcuts might not apply to a new view, so that is not where those belong. You may have other reasons of course - but I'd suggest you might consider just defining these in XAML.
-Added, in response to your comment-
You're quite right - and I've seen some rather large WPF UX projects that tried hard to avoid any code-and wound up unnecessarily obtuse. I try to just use whichever approach yields a working result, and is as simple as I can get it.
Here is a sample snippet that simply creates the MenuItem..
<Menu x:Name="miMain" DockPanel.Dock="Top">
<MenuItem Command="{Binding Path=MyGreatCommand}" Header="DoSomething"/>
That creates the menu. Here, MyGreatCommand is an ICommand, and is simply a property on the view-model. I generally place that within a DockPanel, to handle the StatusBar, etc.
To assign the key gesture..
<Window.InputBindings>
<KeyBinding Key="X" Modifiers="ALT" Command="{Binding Path=MyGreatCommand}"/>
However, since you mentioned that you've already searched for answers and found only XAML - I assume you've already tried this approach. I have used RoutedUICommands instead of user-defined ICommands, to get that nice right-aligned key-gesture in the header text, but I haven't found how to do both. If you insist upon creating the commands and key-gestures all in code, you may have to create RoutedUICommands.
Why are you wanting to set the key-gestures in other than your XAML?
If you want some menu-items to appear only when certain states hold sway within your view-model, then you can bind the Visibility property of a menu-item (which can contain other menu-items) to Collapsed or Visible.
I have a WPF / XAML Window that contains a ComboBox that is giving me problems.
The window's ComboBox is firing off the SelectionChanged event.
The Debugger callstack shows me that SelectionChanged is being called (indirectly) from the Window Constructor.
The problem is that the window has an event Window_Loaded, which does some final initialization of data-members. Because this final initialization isn't done yet, the SelectionChanged event fails with a null-reference exception.
There are several ways I could solve this, but I'd like to know the "most correct" way.
I could fully initialize all my data members in the constructor. This violates the concept of keeping constructors minimal.
I could code the SelectionChanged event handler to properly deal with some data-members being null. This is coding to deal with only a startup problem that will never occur once the Window is fully constructed.
I could make the data-members Lazy-Loaded, so they are not initialized by Window_Loaded, but rather when they are first accessed. Seems like a bit of work to solve a problem that could be solved more simply.
I assume I'm not the first person to deal with UI-events prior to the Window Loaded event. What is the preferred way to handle this?
I had a similar problem and while tracing through it I had an "a-ha" moment. I had a default value as "IsSelected", an OnChange event, and a custom loadSettings method. I blamed the settings method at first, but it turned out that having a default value selected triggered the OnChange event before a handful of the controls were loaded, including the parent combobox which was triggering the null reference. Once I removed the "IsSelected" and allowed the default value to be null/empty it worked fine and my loadSettings method took care of setting the default or last used value.
I usually deal with the (endlessly irritating) SelectionChanged problem like this:
bool mySettingSelectionChangedInCode;
private void SetMySettingComboBox(string value)
{
mySettingSelectionChangedInCode = true;
mySettingComboBox.SelectedItem = value;
mySettingSelectionChangedInCode = false;
}
private void mySettingComboBox_SelectionChanged(object sender, EventArgs args)
{
if (mySettingSelectionChangedInCode)
return;
//...
}
The most proper way would be to build your application using MVVM pattern. In that case you would not have to deal with those problems. But I realize that it is not always possible to just move to MVVM unless the project is in its very beginning state.
Anyway, the problem you describe I would solve by defining a flag like IsInitialized in your window and set it to true once you've completed the initialization in the Loaded event handler. Then, I would check that flag in the SelectionChanged handler and if it is False then return from method without doing anything (ignore the call).
This worked for me:
if (System.Windows.Application.Current.MainWindow.IsInitialized == true)
{
//Do something
}
This could happen because you've set the SelectedValue of the combobox after setting the ItemsSource property.
In my app, I have a group of 3d objects and they're exposed to the user through a TreeView. When a user selects an item in the TreeView, an SelectedItemChanged event is fired, the corresponding 3d object is set to be selected and is highlighted in the 3d render window. This works fine.
What I'm having trouble with is the reverse. In a section of my code, I programatically set the selected 3d object in the scene. I want to reflect the currently selected object in the TreeView, so I run through the items until I find the corresponding one. But once I get to it, I can't find a way to make the item appear selected without having SelectedItemChanged being called, which is not what I want.
Is there a way to do this?
Thanks!
I take it you want to suppress the code in your event-handler? If so, a common way of doing this is with a boolean flag (or sometimes an int counter):
bool updatingSelected;
void SomeHandler(object sender, EventArgs args) { // or whatever
if(updatingSelected) return;
//...
}
void SomeCode() {
bool oldFlag = updatingSelected;
updatingSelected = true;
try {
// update the selected item
} finally {
updatingSelected = oldFlag;
}
}
Would it be appropriate to remove the TreeView's SelectedItemChanged event handler temporarily, and re-add it once you've performed the necessary operations? I haven't tried it myself, but it's the only other thing I can think of (Marc Gravell beat me to my original answer - I've done THAT before ;) ).
Good luck!