I have a simple button, and I would like it to have the behavior of a "sticky" button, such as it visually has two states (much like a switch). In other words, I would like it to maintain its pressed style even after I release the mouse button, but it would have to be clickable.
Is there a way to easily do it in WPF? I'm using Fluent but I could change it if needed.
<Fluent:Button Header="myButton" Command="{Binding ToggleCommand}" SizeDefinition="Small">
<Fluent:Button.Icon>
<Grid Width="16" Height="16">
<Image Source="../Images/16x16/icon.png"/>
</Grid>
</Fluent:Button.Icon>
</Fluent:Button>
Something like this:
1-
2-
PS:
I'm using MVVM, and in the Command, I'm checking and changing a bool flag to know what state the button currently is. I would be able to bind it to a button property, if it helps in any way.
Turns out there is a component ToggleButton that does exactly what I was looking for. Silly me for not knowing it existed in the first place.
Related
I am creating a custom ToolBar control that I will be able to use in my WPF application.
My CustomToolBar control is based on a StackPanel control and will contain multiple CustomToolBarButton controls.
My CustomToolBarButtons are based on a vertical StackPanel and contain an Image control and a Label control.
I want to programmatically create a Click event for my CustomToolBarButtons that will fire when Image or label gets clicked. Unfortunately, neither Image nor Label controls have Click events in WPF.
It's a surprise for me because I am used to WinForms controls and vast majority of them have Click events by default. Do I have to create custom Image and Label controls and create Click events for them or is there a cleaner and simpler way of doing this?
Thanks for any help!
Well, there's not a great and simple way using standard functionality. I got around this by making my own trigger (derived from System.Windows.Interactivity.TriggerBase from the Blend SDK) so that in my projects I can do the following:
<Label>
<i:Interaction.Triggers>
<mu:MouseTrigger MouseButton="Middle" MouseAction="Click">
<i:InvokeCommandAction Command="{Binding Path=Close}" />
</mu:MouseTrigger>
</i:Interaction.Triggers>
</Label>
Effectively the MouseTrigger class will handle MouseDown and MouseUp events from the UIElement it's attached to and use that to invoke the actions associated with the trigger. The code is a bit more complex than just that, though, since I also do mouse capturing & I utilize an internal helper class so that I could add multiple triggers to the same element with only one instance of a helper class handling events & capturing the mouse for that element.
For the actual actions, I just use existing Blend or Prism actions such as InvokeCommandAction.
Here is the project if you're interested. It was too large to paste into this format. It uses some C# 6.0 features but you could easily modify it to work on an older version of C# by removing some null conditional operators. It requires you install the Blend SDK as it depends on System.Windows.Interactivity (should install with Visual Studio as long as you select that option). MouseTrigger is the publicly visible class that is the point of interaction with the functionality. MouseCatcher is the internal helper class mentioned.
I'd recommend not going down the route of custom controls, but rather of using some mechanism (this one or otherwise) to extend the existing functionality using the attached property framework that WPF and XAML bring to the table.
In WinForms you would create custom controls simply to get a nonstandard look, in WPF this is no longer necessary.
The WPF way: instead of making a Stackpanel's children implement Click behaviour, let's make a Button look like the desired Stackpanel.
Could do this setting the Content:
<Button>
<StackPanel >
<Image Source="C:\myFiles\myPic.png"/>
<Label HorizontalAlignment="Center">SomeTxt</Label>
</StackPanel>
</Button>
But this way it still looks like a Button, to overcome this we can set the Template instead:
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<StackPanel >
<Image Source="C:\myFiles\myPic.png"/>
<Label HorizontalAlignment="Center">SomeTxt</Label>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
Both solutions will raise a Click event clicking the image or label.
I'm sorry for this being so wordy, but I want to make the situation perfectly clear. Please, if you are a WPF pro, take a look.
There are two CollectionViewSource bound to ItemsControls that use UserControl Views to display either StackPanels or custom Buttons. There is one for each side shown in the screenshot below. The problem I'm encountering is that when the parent collection property is set, all of the buttons in the DataTemplate view are disabled. Even the 2 buttons higher up are having the same problem even though they worked before my recent edits.
If I click on the form, or press any key, the buttons enable. As soon as the property is reset to a newly edited and sorted collection, they disable again. Rinse and repeat. This is what it looks like. The first frame is how it starts (gray using StackPanel), the 2nd is what it looks like when the RFS button is clicked, and the 3rd frame is what happens when I click anywhere or press a key.
I've been going in circles trying out things. The only thing that seems to work is a code-behind workaround that sets focus to one thing and then back. However, that would not be good for the user if they are trying to use one of the other dashboard items.
Since the WPF for all of this is very massive, I'll try to include just the relevant parts. These are the ItemsControls on the TabItemControl (UserControl).
<!-- BID (SELL) DEPTH -->
<ItemsControl Grid.Row="0" Grid.Column="0" x:Name="bidDepthList" ItemsSource="{Binding Source={StaticResource BidDepthCollection}}"
Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
<v:DepthLevelRowView x:Name="BidDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- ASK (BUY) DEPTH -->
<ItemsControl Grid.Row="0" Grid.Column="1" x:Name="askDepthList" ItemsSource="{Binding Source={StaticResource AskDepthCollection}}"
Visibility="{Binding Path=IsMultilegEnabled, Converter={StaticResource CollapsedConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewmodels:DepthLevelViewModel}">
<v:DepthLevelRowView x:Name="AskDepthLevelRowViewControl" DataContext="{Binding}" HorizontalAlignment="Center" Margin="1,0,1,3" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The view being used has 4 "controls" inside of a grid. Only one of them is displayed at a time depending on the state (RFS OFF/RFS ON) and which side they are on (Sell/Buy). The others are collapsed. As you can see, this is fine.
The only common factor between them is that they have their Command set, as do most of the controls at the top that are disabling/enabling correctly. The fact that the buttons enable correctly if any mouse or keyboard action is taken tells me that the CanExecute handler is working, just not immediately. The other controls started working after I made these changes, but then the big buttons started misbehaving like the depth buttons have been doing.
I've tried using CommandManager.InvalidateRequerySuggested(); after altering the collections, but that didn't help.
NOTE: This is also happening even for something as simple as this:
<Button x:Name="TestBuyButton" Command="{x:Static ptcommands:OrderCommands.Buy}" CommandTarget="{Binding RelativeSource={RelativeSource Self}}" Content="Test Buy" />
I know the booleans are set correctly because I added test CheckBoxes to display the current value with IsChecked. And, they all enable, including the extremely basic Button, as soon as any input action is taken.
Is there something else I'm missing or a different approach I can take?
EDIT: I ended up using the Dispatcher to invoke my display update routine from the event thread over to the UI thread. The boolean values get set, but WPF still didn't requery the Command. BUT.. the CommandManager.InvalidateRequerySuggested() call then worked! The only thing I don't like about that is it sounds like a broadcast invalidation. I don't want all commands to be requeried. I just want the Buy and Sell commands to requery. I've tried all sorts of weirdness trying to get just those to work, but nothing has worked so far other than the global.
EDIT 2: It appears as though the InvalidateRequerySuggested is the way to go.
From MSDN CommandManager.InvalidateRequerySuggested:
The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.
This would explain why the focus changes and keypresses would cause the buttons to enable. The page also shows putting the call in a timer. Because of this, I assume it is not as resource intensive as I thought. So, I guess it's my permanent solution.
I had to make a few changes to get the buttons to enable without focus changes.
The boolean values were being set within a method called from an event thread. I had tried calling CommandManager.InvalidateRequerySuggested along with it, but nothing happened. Eventually I thought it might have something to do with the thread. This is what ended up resolving it.
In the parent form that the tab manager class (containing the event handlers and other logic) has access to, I added an invoke method:
Public Sub InvokeOnUiThread(ByRef uiAction As Action, Optional ByRef doAsync As Boolean = False)
Dim dispatchObject As Dispatcher = orderTicketView.Dispatcher
If (dispatchObject Is Nothing OrElse dispatchObject.CheckAccess()) Then
uiAction()
Else
If doAsync Then
dispatchObject.BeginInvoke(uiAction, DispatcherPriority.Normal)
Else
dispatchObject.Invoke(uiAction, DispatcherPriority.Normal)
End If
End If
End Sub
In the tab manager event handler, I changed it to call the update routine through the invoker:
formOrderTicketView.InvokeOnUiThread(New Action(AddressOf UpdateButtons))
At the bottom of the UpdateButtons method, I added a call to the CommandManager if a change has been made that would require a requery:
CommandManager.InvalidateRequerySuggested()
I do not know what type of performance hit this would have, but apparently WPF executes it in its own way when focus changes and such. Calling it directly is the advised way to force it.
From MSDN CommandManager.InvalidateRequerySuggested:
The CommandManager only pays attention to certain conditions in determining when the command target has changed, such as change in keyboard focus. In situations where the CommandManager does not sufficiently determine a change in conditions that cause a command to not be able to execute, InvalidateRequerySuggested can be called to force the CommandManager to raise the RequerySuggested event.
Since it is working now, I am taking this as the resolution.
The command that's bound to the button...is there a CanExecute delegate with it? If so, you have to raise CanExecuteChanged when that delegate should be re-evaluated. Also, make sure the implementation of CanExecute isn't broken and incorrectly returning false;
I have the following code that adds a context menu to a textbox on the UI. The user is supposed to be able to bring up the context menu and select a new units to be used. So the method CurrentUnits in my view model is bound to the textbox. I want a context menu populated by all the potential units. So the method Units in my view model returns a string[] of unit options; such as inches, cm, feet, meters, etc. When the user selects one the method NewUnits_Click is invoked. All works fine, however the contextmenu does not go away when the user selects a menu option. Pressing somewhere else on the screen like the application menu bar will then clear it. Has anyone else seen this problem, or see something wrong with the code below. It seems to have something to do with the ItemTemplate/DataTemplate I have, as creating an set of menu items by hand works fine.
<TextBlock Width="100" Text="{Binding CurrentUnits}" TextAlignment="Right">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu ItemsSource="{Binding Units}">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="NewUnits_Click" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
If you aren't using MVVM when working with WPF, I highly suggest you to start doing so. And even if you are not, I suggest that instead of binding to Click you instead bind on the Command property which wants something that implements ICommand. I think that the behavior you are getting is intended, buttons and menu items in WPF are intended to bind to Commands, it's not just WinForms 2.0.
Another solution would be to hide the Context menu in the code-behind. Perhaps this resource will help you in achieving that.
What i am basically looking out for is a combination of controls that work as 1 whole. I have no idea what the best way would be to start solving this problem in WPF, either a custom control, existing control, slider...?
Only thing i do not want are 3th party controls and the such.
When a certain condition is met a button with text will be placed inside the slider. Every time when certain conditions are met this situation will keep on happening and buttons will be placed inside the border field.
So it could be possible i have like 10 buttons after each other inside the border. The 2 navigation buttons to the left and right serve as navigation between all those buttons so all can actually get view and pressed when needed for further actions.
Picture that illustrates what i wish to achieve:
You can start with something simple as a StackPanel (you dont need to put it in a page but i wanted to make it copy paste friendly):
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Button Content="Left"/>
<ListBox>
<ListBoxItem>btnTest 10:00h</ListBoxItem>
<ListBoxItem>btnTest 11:00h</ListBoxItem>
</ListBox>
<Button Content="Right"/>
</StackPanel>
</Page>
Then focus on the appearance and behavior separately.
You need to learn WPF styles, so you can get the colors and layout as in your sample picture. This will also let you make the ListBox horizontal.
Look into the concept of a ViewModel to learn how to populate the ListBox with items. And event handlers for the buttons.
Its a very broad question but I hope this gives you a start.
I cannot for the life of me figure out how to change a ToggleButton image when clicked. I have looked up countless examples and they are all outdated and no longer work. Or if they do I cannot get them to work. Does anyone have an up to date example I could look at or any suggestions?
I tried doing it in the code behind first. The example I found used a BitmapImage but this is not possible anymore as the BeginInit method cant be used due to security reasons.
Next I tried numerous style triggers but I get way to many compile errors even when they are directly copied and modified to fit the correct parameters. So I am stuck. I cant figure out how to use an EventTrigger to do it nor do any older examples seem to work. Anyone have any ideas?
Why not something like:
<ToggleButton x:Name="b">
<Image Src="myImage.png" Visibility="{Binding ElementName=b,Path=IsChecked,Converter="{StaticResource BooleanToVisibilityConverter}}"/>
<Image Src="myOtherImage.png" Visibility="{Binding ElementName=b,Path=IsChecked,Converter="{StaticResource BooleanToVisibilityConverter,ConverterParameter=Invert}}"/>
</ToggleButton>
Where you have a boolean to visibility converter that can accept a parameter to invert the bool.
Edit:
You'll need to define a converter so that it can convert the bool? from the IsChecked property to a Visibility enum. That's what all the binding code does. There is a basic implementation here that will convert to Visibility.Visible when true and Visibility.Collapsed when false. You need to add a check for the parameter so that it inverts the visibility when Invert is passed (to toggle between two images).
The other way to do this is to define images in the style and use the visual states for Checked and Unchecked to flip flop the images. You can apply a style to multiple buttons but it's hard to vary the images per-button (what my solution does).
This is how you set up a Resource
XAML
<!-- Place this in your window -->
xmlns:converters="clr-namespace:NameSpace"
<!-- Place this above your root UI -->
<Window.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
Then use the converter here BooleanToVisibilityConverter
you can use ImageToggleButton and ImageToggleButtonSideBySide (if you want a Mac-style toggle [or 2-state radio could call it] button) from ImageButtons project of ClipFlair project codebase