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.
Related
I noticed that in a combobox which has some itemsource attached to it, when there is no selected item it tends to scroll to middle of the item instead of starting at the top (first item) and when an item is selected it does sometime scroll to the selected item.
So I want to scroll to first item when no item is selected. For that I tried following fixes.
Code
private void ComboBoxKeyboardSelectionBehavior_DropDownOpened(object sender, object e)
{
var comboBox = (ComboBox) sender;
if(comboBox.SelectedIndex == -1)
{
//var scrollviewer = comboBox.GetScrollViewer();
//scrollviewer.ChangeView(0, 0, null);
//var allItems = comboBox.Items.ToList();
//var cccc = comboBox.Items.Count;
//var firstItem = allItems.First();
var ci = comboBox.ContainerFromIndex(0) as ComboBoxItem;
if (ci != null)
{
ci.StartBringIntoView();
}
}
else
{
var ci = comboBox.ContainerFromIndex(comboBox.SelectedIndex) as ComboBoxItem;
if (ci != null)
{
ci.StartBringIntoView();
}
}
}
WinRTXamlToolkit.Controls.Extensions gave me the option to get scrollviewer and then try the ChangeView method but that isn't working. I got the First item from the list successfully and used the ContainerFromItem method but it returned me null. So I also tried ContainerFromIndex method and provided index as 0 because that is supposed to be the first item, but that aint working either.
In case of selected item (else statement) it is working fine with ContainerFromIndex(comboBox.SelectedIndex) but just to test when I tried it with ContainerFromItem it returned null.
Just FYI, this event is in an attached behavior to the combobox style but that shouldn't matter because behavor works flawlessly for selected item scenario.
If you want to scroll to the first item when there is no selected item, you need to change the behavior of the DropDown of a ComboBox instead of ScrollViewer of a ComboBox.
The DropDown of a ComboBox is acutally a Popup, and the position where to show the Popup is defined in the code behind, and we can’t access to it. One workaround is finding the Popup and relocate it when it is opened, but using this method we need to calculate the VerticalOffset property each time when it is opened, and there are quite many scenarios for different value of VerticalOffset.
Therefore, we suggest you custom a control whose behavior like a ComboBox and position to the first item when no item is selected. For example:
Create a UserControl:
<Button x:Name="rootButton" BorderBrush="Gray" BorderThickness="2" Click="Button_Click" MinWidth="80" Background="Transparent" Padding="0">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Width="{Binding ElementName=rootButton, Path=ActualWidth}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind selectedItem, Mode=OneWay}" Grid.Column="0" VerticalAlignment="Center" FontSize="15" HorizontalAlignment="Center" />
<FontIcon Grid.Column="1" FontSize="12" FontFamily="Segoe MDL2 Assets" Glyph="" HorizontalAlignment="Right"
Margin="0,10,10,10" VerticalAlignment="Center" />
</Grid>
<FlyoutBase.AttachedFlyout>
<MenuFlyout Placement="Bottom" x:Name="menuFlyout">
<MenuFlyoutItem Text="Item 1" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 2" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 3" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 4" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 5" Click="MenuFlyoutItem_Click" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
And the code behind in this UserControl:
public sealed partial class CustomComboBox : UserControl, INotifyPropertyChanged
{
public CustomComboBox()
{
this.InitializeComponent();
selectedItem = "";
}
private string _selectedItem;
public string selectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("selectedItem"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
var item = sender as MenuFlyoutItem;
selectedItem = item.Text;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
FlyoutBase.ShowAttachedFlyout(sender as Button);
}
}
You can use the CustomComboBox in other page like this:
<local:CustomComboBox VerticalAlignment="Center" HorizontalAlignment="Center" />
By defalult this CustomComboBox will show its DropDown list under it.
In addition, you can also consider using other control such as a ListBox to directly replace the ComboBox to avoid the situation.
Update:
Currently, the ComboBox control does not provide related APIs for setting the starting position of DropDown in a Style, but we have a workaround to get the ScrollViewer inside the DropDown,and then call the ChangeView method to change the position. For example:
Define a custom combo box inherited from ComboBox class to get the ScrollViewer:
public class TestComboBox : ComboBox
{
public ScrollViewer InternalScrollViewer;
protected override void OnApplyTemplate()
{
InternalScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
base.OnApplyTemplate();
}
}
Call the ChangeView method to change the position after items are initialized by using Task.Delay() in the ComboBoxKeyboardSelectionBehavior_DropDownOpened event handler:
await Task.Delay(50);
comboBox.InternalScrollViewer.ChangeView(0, 0, 1);
Note, use TestComboBox instead of ComboBox in XAML.
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);
}
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.
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 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);
}