Inactive commands in wpf Toolbar - c#

I have application with toolbar (Add and Delete commands) and TabControl. There is VariableGrid control in each tabItem of TabControl.
look image at: http://trueimages.ru/view/cNFyf
<DockPanel >
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Command="{x:Static VariableGrid:VariableGrid.AddRowCommand}"/>
<Button Content="Delete" Command="ApplicationCommands.Delete" />
</ToolBar>
</ToolBarTray>
<TabControl x:Name="tc">
<TabItem Header="Tab 1">
<vg:VariableGrid ItemsSource="{Binding Items1, Mode=TwoWay}"/> </TabItem>
<TabItem Header="Tab 2">
<vg:VariableGrid ItemsSource="{Binding Items2, Mode=TwoWay}"/>
</TabItem>
</TabControl>
<DockPanel >
Toolbar commands are implemented in my control:
public partial class VariableGrid : DataGrid, INotifyPropertyChanged
{
public static RoutedCommand AddRowCommand = new RoutedCommand();
public VariableGrid()
{
this.CommandBindings.Add(new CommandBinding(VariableGrid.AddRowCommand, AddRow));
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, R emoveRow, CanRemoveRow));
}
private void AddRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void RemoveRow(object sender, ExecutedRoutedEventArgs e)
{
…
}
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = (SelectedItems.Count > 0);
}
}
There are few cases when commands in toolbar are disabled:
when application is running
when I click on gray field of DataGrid
when DataGrid is empty
When any row of DataGrid is selected - commands of toolbar are becoming active.
Can you help me with my issue? What CommandTarget of toolbar buttons should I set?
PS: There are two VariableGrids in my application. Thats why I can't set CommandTarget as "{Binding ElementName=variableGrid}". I think it should be set to FocusedElement. But I don't know how to do this.

WPF should be calling your CanRemoveRow method every once in a while to check if it is ok to remove a row. You should put a Boolean condition in this method that will answer that question. If you want similar functionality for your AddRowCommand, add a CanAddRow method where you bind the AddRowCommand.
You may want to read the Commanding Overview at MSDN.
UPDATE >>>
Oh... do you want to know what code to use for these disabled conditions? I'll assume so:
when application is running
I'm guessing you mean 'when application is busy'... add a Boolean property named IsBusy, set it to true when the application performs any long running processes, then add !IsBusy into your method condition.
when I click on gray field of DataGrid
when DataGrid is empty
Both of these conditions can be judged using the SelectedItem property of the DataGrid by adding && dataGrid.SelectedItem != null into your method condition.
Therefore, you want something like the following:
private void CanRemoveRow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !IsBusy && SelectedItem != null);
}

Related

ContextMenuOpening event fired for both TreeView and TreeViewItem

My application uses TreeView populated with custom nodes defined in TreeView.ItemTemplate. Content of each node is wrapped into StackPanel with Node_ContexMenuOpening event that populates context menu based on some application properties, which is working.
XAML:
<TreeView x:Name="treeNodes" ContextMenu="{StaticResource EmptyContextMenu}" ContextMenuOpening="TreeNodes_ContextMenuOpening">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type c:MyCustomType}" ItemsSource="{Binding MyCustomTypeChildren}">
<StackPanel Orientation="Horizontal" ContextMenu="{StaticResource EmptyContextMenu}" ContextMenuOpening="Node_ContextMenuOpening" >
<Image Source="Frontend\Images\import.png" MaxWidth="15" MaxHeight="15"/>
<TextBlock Width="5"/>
<TextBlock Text="{Binding CustomTypeName}" MinWidth="100"/>
<TextBlock Width="10"/>
<Image Source="CustomImagePath" MaxWidth="15" MaxHeight="15"/>
<TextBlock Width="5"/>
<TextBlock Text="{Binding CustomTypeName2}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind:
private void Node_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
// get context menu and clear all items (empty menu with single placeholder item
// is assigned in XAML to prevent "no object instance" exception)
ContextMenu menu = fe.ContextMenu;
menu.Items.Clear();
// populate menu there
}
I would like to have same functionality on TreeView (treeview specific context menu when right clicking on empty area of treeview), which also works.
private void TreeNodes_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
TreeView tw = sender as TreeView;
ContextMenu menu = tw.ContextMenu;
menu.Items.Clear();
// poopulate menu there
}
But the issue is that TreeNodes_ContextMenuOpening is fired even after right clicking at TreeView node, right after Node_ContextMenuOpening is handled, which overwrites context menu for clicked node. I tried to solve it using:
// also tried IsMouseOver and IsMouseCaptureWithin
if (tw.IsMouseDirectlyOver)
{
// handle TreeNodes_ContextMenuOpening event there
}
but without success. Any suggestions?
Thanks in advance.
You can try using the ContextMenuEventArgs.Handled value. https://learn.microsoft.com/en-us/dotnet/api/system.windows.routedeventargs.handled?view=netcore-3.1#System_Windows_RoutedEventArgs_Handled
Gets or sets a value that indicates the present state of the event handling for a routed event as it travels the route.
Example
protected override void OnPreviewMouseRightButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true; //suppress the click event and other leftmousebuttondown responders
MyEditContainer ec = (MyEditContainer)e.Source;
if (ec.EditState)
{ ec.EditState = false; }
else
{ ec.EditState = true; }
base.OnPreviewMouseRightButtonDown(e);
}

How to allow pressing only the cancel button when the value is invalid?

I have a simple dialog with a SpinEdit and two buttons: OK_Button and Cancel_Button. I've set a mask for the value in the SpinEdit and the dialog won't let me press the cancel button when the value is invalid. I've tried changing the SpinEdit's property to InvalidValueBehavior="AllowLeaveEditor" but then I can click both, OK and cancel button. Is there a way to ONLY allow pressing cancel when the value is incorrect?
XAML:
<dxe:SpinEdit x:Name="dxSpinEdit"
Height="23" MinWidth="200" Width="Auto"
HorizontalAlignment="Right"
Text="{Binding Value, Mode=TwoWay}"
MaskType="Numeric"
IsFloatValue="{Binding FloatValue}"
MinValue="{Binding MinValue}"
MaxValue="{Binding MaxValue}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"
MaskShowPlaceHolders="{Binding ShowPlaceHolder}"
InvalidValueBehavior="WaitForValidValue"
/>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" PreviewMouseDown="win_PreviewMouseDown" />
</StackPanel>
I looked up this issue on the devexpress forum but their solution didn't work for me. I've implemented the MouseDownPreview event like so:
C# (code behind)
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void win_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if(e.Source == Cancel_Button)
{
DialogResult = false;
Close();
}
}
But the event wasn't handled at all. I'd like to keep the property InvalidValueBehavior at the value "WaitForValidValue" but at the same time I'd like to allow pressing the Cancel button.
Even if you're not going to go the full MVVM route, you should switch from using click events to an ICommand implementation that supports CanExecute logic (such as this one from MVVM Light).
Using a command will automatically disable any bound control (e.g. button or menu item) when CanExecute is false. You can then have all the logic for controlling your commands grouped in one place, including validation that will only allow OK to be clicked when your object is in a valid state.
If you just want to go the standard WPF (non MVVM) route, you could add something like this in your window's constructor
public MyView()
{
....
Ok_Button.Command =
new RelayCommand(() => DialogResult = true, // just setting DialogResult is sufficient, no need to call Close()
// put the required validation logic here
() => dxSpinEdit.Value > 0 && dxSpinEdit.Value < 10);
Cancel_Button.Command = new RelayCommand(() => DialogResult = false);
// replace this with the actual event from SpinEdit
dxSpinEdit.ValueChanged += (s,e) => (OK_Button.Command as RelayCommand).RaiseCanExecuteChanged();
}
Yes I know it looks ugly 😀 - I'd suggest following the MVVM design pattern instead. When using MVVM, all of the command functionality belongs in your ViewModel.
Either way, you can then remove all the click and mousedown handlers from your buttons.

WPF TabControl no selected Item on start

I am using a WPF tabcontrol to display items which are bound from a viewmodel.
By default on start the first item of the list is selected but I want no item to be selected on start. I can set the SelectedItem in the OnSelectionChanged event to null then no item is selected on start but then it is no longer possible to manually select a item.
public partial class ProjectScopeMain : Window
{
private bool firstStart = true;
public ProjectScopeMain()
{
this.Initialized += this.ProjectScopeMain_Initialized;
this.InitializeComponent();
}
private void ProjectScopeMain_Initialized(object sender, System.EventArgs e)
{
this.TabControlSettings.SelectionChanged += TabControlSettingsOnSelectionChanged;
}
private void TabControlSettingsOnSelectionChanged(object sender, EventArgs e)
{
this.TabControlSettings.SelectedItem = null;
}
private void ButtonCreate_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.Close();
}
}
My XAML Code. SelectedIndex=-1 does not work
<customControls:TabControl x:Uid="tabControlSettings" x:Name="TabControlSettings"
prism:RegionManager.RegionName="{x:Static infrastructure:RegionNames.ProjectScopeTabsRegion}"
TabStripPlacement="Left" Style="{DynamicResource TabControlStyle}"
ItemContainerStyle="{DynamicResource TabItemVerticalProjectScopeStyle}" SelectedIndex="-1"/>
I don't believe the tab control lets you have nothing selected. An easy work around for this is to create an empty tab with a collapsed visibility, and navigate to it when you would otherwise wish to clear your tab control. This will result in a tab's content being shown (which in this case is empty) and no header being present.
<TabControl Name="MyTabControl" SelectedIndex="0">
<TabItem Header="" Visibility="Collapsed">
<!--There's nothing here-->
</TabItem>
<TabItem Header="Item 1">
<TextBlock Text="Some item 1" />
</TabItem>
<TabItem Header="Item 2">
<TextBlock Text="Some item 2" />
</TabItem>
</TabControl>
You could 'clear' it with:
MyTabControl.SelectedIndex = 0;
Since you wish to bind the child items, I would imagine you will need to combine the children in a resource first.
You can deselect any TabItem by setting its IsSelected property to false. The content of TabControl will be blank once none of its TabItems are selected.
Subscribe to the Loaded event of the TabControl then set SelectedItem to null:
<TabControl Loaded="TabControl_OnLoaded">
<TabItem Header="page 1" Content="page 1" />
<TabItem Header="page 2" Content="page 2" />
</TabControl>
private void TabControl_OnLoaded(object sender, RoutedEventArgs e)
{
((TabControl)sender).SelectedItem = null;
}
It will work even if you bind SelectedItem to a property in your ViewModel, but there will be a moment after loading the page that you'll get a non-null value there, and after that null, but from what I've seen it didn't cause any weird flickering or anything so it's probably fine.
Tested on .NET Framework 4.8
You can select nothing by setting SelectedTab property nullptr.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.tabcontrol.selectedtab?view=net-5.0
A TabPage that represents the selected tab page. If no tab page is selected, the value is null.

ListPicker SelectionChanged Event Called Multiple Times During Navigation

I have a SelectionChanged event in a ListPicker within one of my application Pages that fires multiple times before the page is loaded. This is really inconvenient for me as when an item is selected, a MessageBox is displayed (and other actions will be performed). The MessageBox is displayed twice every time the page is NavigatedTo. How can I fix this?
XAML
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>
XAML.CS
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex != -1)
{
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
}
}
well, that's how a listpicker behaves, what best you can do is instead of making ThemeListPicker_SelectionChanged make a parent stackpanel inside the datatemplate somewhat like this
<Listpicker.ItemTemplate>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
</Listpicker.ItemTemplate>
<Listpicker.FullModeItemTemplate>
<DataTemplate x:Name="PickerFullModeItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
<Listpicker.FullModeItemTemplate>
now use this tap stk_Tap to do your action as, this event would also get called every time the selection changed gets called but, it wont exhibit the buggy behavior like that of selection changed event.
hope this helps.
Attach the SelectionChanged event after the ListPicker is Loaded.
...
InitializeComponent();
YourListPicker.Loaded += YourListPicker_Loaded;
...
private void YourListPicker_Loaded(object sender, RoutedEventArgs e)
{
YourListPicker.SelectionChanged += YourListPicker_SelectionChanged;
}

I have a context menu which is being shared by 6 different labels, how can I tell which label is using the current instance of the context menu?

Here is the xaml for the contextMenu:
<Window.Resources>
<ContextMenu x:Key="IBContextMenu" x:Shared="true" Name="IBContextMenu1">
<MenuItem Header="Edit" Click="ibEdit_Click" AllowDrop="False" />
<MenuItem Header="Clear" Click="ibClear_Click"/>
</ContextMenu>
</Window.Resources>
Both the edit and clear items' methods need to know which label to act upon. How can I do this?
I think you are looking for PlacementTarget:
http://msdn.microsoft.com/en-us/library/system.windows.controls.contextmenu.placementtarget.aspx
If you switch over to a Command-pattern, you can actually get this via Binding and pass it along as the CommandParameter...
Here's an answer I came up with. I don't really like it because it's a bit hack-ish, but it works. The idea is that you make your labels listen to the MouseRightButtonUp event, which is fired when the user releases the right mouse button after clicking to open the context menu. In the event handler, you set a private Label variable to the label that the user just right-clicked. Then, in your MenuItem click handler, you can access that private Label variable. Note that all the labels you want to do this must use the same event handler for MouseRightButtonUp.
For example:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="Edit" Click="Edit_Click"/>
<MenuItem Header="Clear" Click="Clear_Click"/>
</ContextMenu>
</Window.Resources>
<StackPanel>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some text</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some junk</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some stuff</Label>
<Label ContextMenu="{StaticResource MyMenu}"
MouseRightButtonUp="Label_MouseRightButtonUp">Some 0000</Label>
</StackPanel>
Code behind:
private void Edit_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private void Clear_Click(object sender, RoutedEventArgs e)
{
if (clickedLabel != null)
{
MessageBox.Show(clickedLabel.Content.ToString());
}
}
private Label clickedLabel;
private void Label_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
clickedLabel = (Label)sender;
}
Try to set a DataContext to the Labels, for example
And in the Click event just check the ((FrameworkElement)sender).DataContext for FIRST/SECOND etc. Let us know if that works.

Categories