ContextMenuOpening event fired for both TreeView and TreeViewItem - c#

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);
}

Related

How do I handle SelectionChange when the ComboBox is

I have a ListBox, where the list element has a ComboBox, a TextBox and a slider. Depending on the selction of the ComboBox either the TextBox or the slider should be visible.
<ListBox Name="lstPWM" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<!-- more definitions -->
</Grid.ColumnDefinitions>
<ComboBox ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding GeberVisible}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding WertVisible}" Value="{Binding Wert, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind is:
public partial class MainWindow : Window
{
public ObservableCollection<PWMKanal> PWM_col { get; set; } = new();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lstPWM.ItemsSource = PWM_col;
foreach (var item in Board.PWM) PWM_col.Add(item); //Board.PWM is the data source.
}
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox box = sender as ComboBox; // Finding the line in the ListBox.
PWMKanal PWM = box.DataContext as PWMKanal;
int z = PWM_col.IndexOf(PWM);
Board.PWM[z].Gebertyp = (QuellePWM)box.SelectedValue;
if (Board.PWM[z].Gebertyp == QuellePWM.Sender)
{
PWM_col[z].GeberVisible = Visibility.Visible; // I thought that i may change the
PWM_col[z].WertVisible = Visibility.Hidden; // ObservableColelction directly
} // but the display is not updated.
else // In Debug mode i see, that PWM_coll
{ // is changed as expected, but no effect
PWM_col[z].GeberVisible = Visibility.Hidden; // on the GUI.
PWM_col[z].WertVisible = Visibility.Visible;
}
if (PWM_col.Count != 0) // this code is intended to update the GUI, but every time
{ // a new item is added the Selection Change fires again
PWM_col.Clear(); // and i get a stack overflow in an endless loop.
foreach (var item in Board.PWM) PWM_col.Add(item);
}
}
}
The comments describe my approaches and problems:
I change the selected element of the ObservableCollection directly, but this has no effect on GUI. At least tho code doesn't crash.
I clear the list ObservableCollection PWM_col, but then i get an infinite loop: every time an element is added to the list the SelectionChange event fires, calling the routin again. Result is stack overflow.
Now my questions to my approaches:
Is it possible to change an element of an ObservableCollection directly by code, and the display is automatically refreshed?
Is it possible to somehow catch the SelectionChanged event before the handler is executed? Or is it possible to temporary dissable the event?
Any other idear?
Thank you for your help!
CollectionChanged does notify, that collection itself, not the
single items, is changed. Therefore to see the changes item's
property need to implement INotifyPropertyChanged. Also remove Mode=OneTime
You can of course set the flag, that PWMTyp_SelectionChanged is
running:
private bool selChangedIsRunning = false;
private void PWMTyp_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(selChangedIsRunning) return;
selChangedIsRunning = true;
// do stuff ....
selChangedIsRunning = false;
}
Other idea is - don't use the SelectionChange event, but do bind
Slider.Visibility and TextBox.Visibility to the
ComboBox.SelectedValue and use value converter to define the
Visibilty, also you can use the ConverterParameter.
<ComboBox x:Name="CmbPWMTyp" ItemsSource="{Binding Path=Gebertyp, Converter={local1:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectionChanged="PWMTyp_SelectionChanged"
SelectedValue="{Binding Path=Gebertyp}" />
<TextBox Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=TBX}" Text="{Binding GeberNmr, Mode=TwoWay}"/>
<Slider Visibility="{Binding ElementName=CmbPWMTyp, Path=SelectedValue, Converter={StaticResource YourConverter}, ConverterParameter=SLDR}" Value="{Binding Wert, Mode=TwoWay}"/>
This link can be also very helpful for you: Difference between SelectedItem SelectedValue and SelectedValuePath

Make specific ListView item not clickable in Universal Windows Platform

I am using a ListView element in my XAML:
<ListView
x:Name="myList"
IsItemClickEnabled="true"
ItemClick="onDrawerItemClick"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListView.ItemTemplate>
<DataTemplate>
<Grid
Width="260">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="44" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<Image
x:Name="image"
Source="{Binding myIcon}"
Grid.Column="0" />
<TextBlock
Text="{Binding myTxt}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
and I populate it using Bind property like so:
List<MyObj> listData = a list with title + image uri;
myList.ItemsSource = listData;
I need to disable click only for some items depending on some value from MyObj in my list but the others to have it. In Android we use adapter for that, how should I handle it here?
First, you should create a new bool property called Disabled inside your MyObj object.
Then, subscribe to myList's ContainerContentChanging event where you have access to the ListViewItem and its corresponding Item, which in this case is your MyObj. So, if MyObj.Disabled is true, make that ListViewItem non-clickable.
private void myList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
var listViewItem = args.ItemContainer;
if (listViewItem != null)
{
var model = (MyObj)args.Item;
if (model.Disabled)
{
listViewItem.IsHitTestVisible = false;
// OR
//listViewItem.IsEnabled = false;
}
}
}
Keep in mind that you might want to use listViewItem.IsEnabled = false if you want that item to appear dimmed. This is because the default ListViewItemstyle has a Disabled state that reduces its Opacity; while setting listViewItem.IsHitTestVisible = false won't change its appearance in any way.
The listView is a strange control as it does not have any mechanism to disable selection.
So what I suggest you do is rather handle the event that notifies the framework that an item has been selected by attaching an event handler to ItemSelectionChanged and in there perform a deselect on the item:
yourListView.ItemSelectionChanged += yourListView_ItemSelectionChanged;
private void yourListView_ItemSelectionChanged(
object sender,
ListViewItemSelectionChangedEventArgs e)
{
if (e.IsSelected)
e.Item.Selected = false;
}
Please let me know if my answer helps :)

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.

Windows Phone 8 ListPicker uses ContextMenu to navigate away yet going back the page is completely empty

We are using a Toolbox:ListPicker control to display an ObservableCollection. We now want to let users optionally navigate through the ListPicker to an editor for the objects in the ObservableCollection.
We added a ContextMenu to the ItemTemplate allowing the user to Navigate to the editor. The user is presented with the context menu after they long-tap on an item. If the user taps on the menu item they are presented with the editor page. After the user presses the back button in the editor, they are returned to a completely blank page - only the system tray is shown. If the user presses the back button again, the ListPicker briefly appears before begin replaced by the page hosting the ListPicker instance.
What is the point of the blank page and is there a way to get rid of it?
Here is the XAML for the ListPicker instance.
<toolkit:ListPicker
x:Name="listPicker_Generators"
Margin="12,-6,0,12"
ExpansionMode="FullScreenOnly"
HorizontalAlignment="Left"
Width="430"
Height="Auto"
VerticalAlignment="Top"
FullModeHeader="{Binding Source={StaticResource LocalizedStrings}, Path=LocalizedResources.label_AccountEditor_Generator}"
>
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" FontSize="36" />
<TextBlock Text="{Binding Notes}" Margin="0,0,0,12" FontFamily="Segoe WP" FontSize="{StaticResource PhoneFontSizeSmall}" />
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu
Visibility="{Binding Visibility}"
Closed="ContextMenu_Closed"
Opened="ContextMenu_Opened">
<toolkit:MenuItem
Name="menuItem_Edit"
Header="{Binding Source={StaticResource LocalizedStrings}, Path=LocalizedResources.label_AccountEditor_EditGenerator}"
Click="menuItem_Edit_Click" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
</toolkit:ListPicker>
Here is the code behind for the context menu handling.
private void menuItem_Edit_Click(object sender, RoutedEventArgs e)
{
AccountViewModel.GeneratorChoice item = ((AccountViewModel.GeneratorChoice)((sender as FrameworkElement).DataContext));
if (item != null)
{
AccountViewModel vm = DataContext as AccountViewModel;
vm.HoldingSelectedGeneratorIndex = listPicker_Generators.SelectedIndex;
NavigationService.Navigate(new Uri("/Views/GeneratorEditor.xaml?id=" + item.Id.ToString(), UriKind.Relative));
// force the selection to change so we get redrawn when we come back
if (listPicker_Generators.SelectedIndex > 0)
listPicker_Generators.SelectedIndex--;
else
listPicker_Generators.SelectedIndex++;
}
}
private void ContextMenu_Closed(object sender, RoutedEventArgs e)
{
AccountViewModel vm = DataContext as AccountViewModel;
vm.ContextMenuOpen = false;
}
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
// a work-around from http://stackoverflow.com/questions/15181441/windows-phone-toolkit-context-menu-items-have-wrong-object-bound-to-them-when-an
ContextMenu contextMenu = (sender as ContextMenu);
FrameworkElement owner = (contextMenu.Owner as FrameworkElement);
if (owner.DataContext != contextMenu.DataContext)
contextMenu.DataContext = owner.DataContext;
AccountViewModel.GeneratorChoice item = contextMenu.DataContext as AccountViewModel.GeneratorChoice;
if (item.Id.Value != 0)
{
bool factorySupplied = item.FactorySupplied == null ? false : true;
if (factorySupplied)
{
contextMenu.Items.OfType<MenuItem>().First(m => (string)m.Name == "menuItem_Edit").Header = AppResources.label_AccountEditor_ViewGenerator;
}
AccountViewModel vm = DataContext as AccountViewModel;
vm.ContextMenuOpen = true;
}
}
If there's no need to navigate back to the FullScreen of the listpicker, just override the back key press on the details page and navigate back to the page hosting the listpicker.
Details Page:
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
e.Cancel = true;
NavigationService.Navigate(
new Uri("ListPickerView.xaml", UriKind.Relative));
}
}
EDIT
I'm afraid that's not possible by using the listpicker. You will have to write a separate page simulating the listpicker fullscreen mode, for that matter. If you examine the control in the toolkit, you'll notice that it internally navigates to a ListPickerPage.xaml page. The state preservation and DataContext assignment to that page is done by the control.
On details page, when you press the hardware back button, the Frame pops the page sitting οn top of the navigation history stack, that is the ListPickerPage.xaml. At that moment, the page has no DataContext passed to it, this is why you see a blank page.

Give focus to a component inside an expander

I have this requirement where I need to focus the first element inside the expander when the user press tab.
Currently (default behavior) the focus goes to the expander, I've tried to focus the first element of the expander by creating a focus event handler in the expander like this:
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded)
{
expander.IsExpanded = true;
this._someText.Focus();
}
}
Which doesn't work.
I've also tried to give the focus the the next element:
var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
var keyboardFocus = Keyboard.FocusedElement as UIElement;
keyboardFocus.MoveFocus(tRequest);
But only works the second time ( when the expander has been at least opened once )
I've tried to put this in a thread and some other crazy ideas.
How can I give focus to the first element inside an expander? ( the first time the expander is closed )
I tried several ways and none of them worked, basically the problem is the TextBox is still rendering when the expander is expanding ( to early ).
So instead what I've found is to add the IsVisibleChanged event to the textbox so when the expander finished the textbox become visible and request the focus
XAML
<Expander GotFocus="ExpanderGotFocus">
<Expander.Header>
<TextBlock Text="{x:Static Client:Strings.XYZ}" />
</Expander.Header>
<Expander.Content>
<StackPanel>
<TextBox IsVisibleChanged="ControlIsVisibleChanged" Name="txtBox" />
</StackPanel>
</Expander.Content>
</Expander>
Code behind
private void ExpanderGotFocus(object sender, RoutedEventArgs e)
{
var expander = (Expander) sender;
if (!expander.IsExpanded )
{
expander.IsExpanded = true;
}
}
private void ControlIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Keyboard.Focus((IInputElement)sender);
}
Check with the following,
XAML code:
<StackPanel>
<Expander Header="Expander"
Name="expander"
Collapsed="OnCollapsed"
IsExpanded="True" >
<StackPanel>
<TextBox Text="Text1" Name="textBox1" />
<TextBox Text="Text2" Name="textBox2" />
<TextBox Text="Text3" Name="textBox3" />
</StackPanel>
</Expander>
<TextBox Text="Text4" Name="textBox4" />
</StackPanel>
in the code behind:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.Loaded += delegate
{
textBox2.Focus();
};
}
private void OnCollapsed(object sender, RoutedEventArgs e)
{
var element = Keyboard.FocusedElement;
if (element != null)
{
//now is the ToggleButton inside the Expander get keyboard focus
MessageBox.Show(element.GetType().ToString());
}
//move focus
Keyboard.Focus(textBox4);
}
}

Categories