I have a user interface with a TabControl that initially displays a start page. Other items can be added to it by double-clicking on content in, for example, a DataGrid. New tabs should be selected when they are created. If the document corresponding to the item in the grid is already open, then the existing tab for that document should be opened rather than creating a new one.
I know that I should be able to programmatically select a tab by setting the TabControl's SelectedItem or SelectedIndex properties. However, the desired tab never actually activates. If I set one and then inspect the TabControl's state in the debugger, then both fields seem to update properly. However, after I continue execution, I see that the selected tab remains unchanged in the UI, and if I pause and inspect the TabControl's state again I see that the SelectedItem and SelectedIndex have returned to their previous values. Selecting a tab by clicking on it in the UI, on the other hand, works just fine.
Here's the declaration for the TabControl:
<TabControl x:Name="Tabs" >
<TabItem x:Name="StartPageTab" Header="Start Page" DataContext="{Binding Path=StartPageViewModel}">
...
</TabItem>
</TabControl>
And the code for adding and selecting tabs:
private void _SelectTab(MyViewModel model)
{
TabItem tab;
if (_TryFindTab(model, out tab)) Tabs.SelectedItem = tab;
}
private bool _TryFindTab(MyViewModel target, out TabItem tab)
{
foreach (TabItem item in Tabs.Items)
{
MyViewModel model = item.DataContext as MyViewModel;
if (model != null && model.Equals(target))
{
tab = item;
return true;
}
}
tab = null;
return false;
}
private void _AddTab(MyViewModel model)
{
TabItem tab = new TabItem { DataContext = model, Content = new MyView() };
Binding bind = new Binding { Source = model, Path = new PropertyPath("Name") };
tab.SetBinding(TabItem.HeaderProperty, bind);
Tabs.Items.Add(tab);
Tabs.SelectedItem = tab;
}
It turned out to be related to something I conveniently omitted from the original problem description:
The DataGrid in question was in the content for StartPageTab. I was handling double-clicks on that DataGrid by capturing its MouseDoubleClick event, searching the visual tree to find what DataGridRow was double-clicked (if any), and then raising an event that would eventually be captured by the main window, which would respond by calling either _SelectTab or _AddTab, depending on whether the document in question was already open.
At which point, the call stack would unroll and get back to that MouseDoubleClick event handler. In that handler, I forgot to set the MouseButtonEventArgs's Handled property to true. So WPF kept searching for someone else to handle that click event - and the element that it eventually found would respond by asking for focus, which in turn meant that the original tab needed to get focus back.
Adding e.Handled = true; stopped that whole mess in its tracks, so the new tab could stay selected.
You could try using tab.focus()
I have tabs in my application and this is a quick way to make your selected tab visible.
Have you tried binding to TabItem.IsSelected and updating that in you view model?
In an older C# app I had, using page controls, I was able to force the page active by telling the tab control object to select the tab...
MyTabControlWithMultiplePages.SelectTab(PageIWantShown);
Related
I have a TextBox inside a ListView. I dynamically add ListView items on KeyDown event (on adding the new item to my observable collection a new ListView Item is created due to Two-Way Binding).
Now when a new element is added I want to set focus to the TextBox of newly created ListView item. It is a bit complex than I thought, Help me solve this problem.
I originally thought it could be possible to use ListView.ContainerFromItem to retrieve the newly added item and then use VisualTreeHelper to search for the TextBox within the template and focus it. It turns out however, that this solution does not work. After adding the item, the container for the list item is not materialized immediately (which is logical, as the control just got the notification about the collection change, but didn't have any time to build up the control hierarchy yet, as our code is still executing).
In fact, the problem has a far simpler solution. You can use the Loaded event on the TextBox within the template. This will be called only once, when the template is first materialized. This is not a perfect solution however, see update below.
In my example I have the following template:
<DataTemplate>
<Grid>
<TextBox Loaded="InputTextBox_Loaded" />
</Grid>
</DataTemplate>
And in the code-behind:
private void InputTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
textBox.Focus(FocusState.Programmatic);
}
Update: Virtualization
Turns out there is a catch - virtualization creates a number of copies of the template in memory (depending on the window size) to allow for comfortable scrolling, but after that it will just reuse the existing controls - and the Loaded event will never be called again - that's a problem. Luckily, we can solve this as well - instead of the Loaded event we will use DataContextChanged.
Updated XAML:
<DataTemplate>
<Grid >
<TextBox DataContextChanged="TextBox_DataContextChanged" />
</Grid>
</DataTemplate>
Updated code-behind:
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if ( args.NewValue == Items.Last())
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
}
}
Ok, that is better, we are getting there! Only one thing left to make it perfect. The current configuration means that once we scroll the last item into view, it will always get focus, which might not be what we want. Instead, we probably want this to happen only once - when the item is newly added. We can do so by adding a bool flag which we set to true when adding a new item into the collection and flip back to false when we focus it the first time:
//set this to true when a new item is being added to the collection
private bool _focusItem = true;
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if (args.NewValue == Items[Items.Count - 1] && _focusItem)
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
_focusItem = false;
}
}
I'm sorry if this question was asked before.
After 1 hour of searching here i could not to find it.
First, i'm using WPF, but not with MVVM. I know MVVM is way to go and i'm learning it. I'm new to programming.
It's small program and i have buttons on mainwindow in one StackPanel, and TabControl (_tabcntrl) in another.
On button click mainwindow generates one tab:
TabItem _tab = new TabItem();
UserControl _uc = new UserControl();
_tab.Content = _uc;
_tabcntrl.Items.Add(tab);
In usercontrol i have one public event
public void test()
{
//some code
}
So my question is how to fire this event from main window (button click in main window), but only in selected tab. Idea is that you can have multiple tabs with same usercontrol.
I know i can do it with
_uc.test();
But only when tab is created.
Also i tried to put
TabItem tb = _tabcntrl.SelectedItem;
tb.test();
In button click event, but i get error.
Stupid thing is that i figured out how to fire event from usercontrol, and i can't other way around. Feeling pretty stupid for asking this in first place.
Thank You, sorry for my bad English
There are various options, you can either look for VisualTree or simply use Children property of the Control and find respective element.
TabItem tb = _tabcntrl.SelectedItem;
var childControls = control.Children.OfType<UserControl>(); // your controltype
// I'm looping through all child controls of type 'UserControl' but you can customize to your case.
foreach(var control in childControls)
{
// execute control logic here
control.test();
}
I am using MVVM Light WPF 4.
I have a ContentPresenter in my Home.xaml.
<ContentPresenter Name="MDI" Content="{Binding WindowName, Mode=OneWay}">
I am binding user control to this in viewmodel like
public UserControl WindowName { get; set; }
void ShowSalesEntry()
{
WindowName = null;
WindowName = new SalesEntry();
RaisePropertyChanged("WindowName");
}
by using command in a menu click and it is binding fine.
Now in the user control i have a button which i used to close (but to close i change the visibility
to collapsed) by this way..
Visibility="{Binding visibility, Mode=OneWay}"
in the user control view model,
public SalesEntryViewModel()
{
visibility = Visibility.Visible;
cmdExitWindow = new RelayCommand(ExitWindow);
RaisePropertyChanged("visibility");
}
and the following to close (visibility to collapsed)
public RelayCommand cmdExitWindow { get; set; }
void ExitWindow()
{
visibility = Visibility.Hidden;
RaisePropertyChanged("visibility");
}
To exit (means visibility collapsed)..
This is working fine upto this.
Problem is when i click the same page i mean to show the same user control,
now this time the visibility is still collapsed. Even though i changed to visible in the
load event.
How to solve this..
I am new to MVVM WPF.. Please help me..
Problem is when i click the same page i mean to show the same user
control, now this time the visibility is still collapsed. Even though
i changed to visible in the load event.
Based on this comment and the code provided, you've either omitted code, or you've confused the purpose of the constructor.
In your constructor, you have set the Visibility to Visible. You then have a method that sets the Visibility to Hidden, but there is nothing to ever set it back to Visible once this has occurred. The constructor only fires when the object is created. You need something to set the Visibility back at the appropriate time (ie. your comment "when i click the same page").
//Add these lines to the method/event that will show the control again
visibility = Visibility.Visible;
RaisePropertyChanged("visibility");
That's the best answer I can give based on what you've provided.
I have a wpf listbox with a custom item template which contains a rectangle.
The each item in the listbox can be selected (only one at a time).
I want to add a behavior in which when a user clicks on a place which isn't the item (for instance, a blank spot on the listbox, which is not an item), the selected item will become deselected.
Any ideas?
Thanks.
For example with a simple listbox:
item 1
item 2
The behavior that I'm looking for is when the user clicks on pixel 500 (which is a part of the listbox but not on an item), the currently selected item will be deselected.
The simple solution is to data bind a property to the ListBox.SelectedItem property and set it to null whenever you want to clear the selection:
<ListBox ItemsSource="{Binding YourItems}" SelectedItem="{Binding SelectedItem}"
SelectionMode="Single" />
Then in code, you can just do this to clear the selection:
SelectedItem = null;
And when would you do that? You can attach a handler to the PreviewMouseLeftButtonDown event of the Window, or any other control in your UI. In the handler method, you could do a hit test to see what the item the user clicked on was:
HitTestResult hitTestResult =
VisualTreeHelper.HitTest(controlClickedOn, e.GetPosition(controlClickedOn));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
See the VisualTreeHelper.HitTest Method (Visual, Point) for more help with this part.
Then maybe something like this:
if (controlUnderMouse.GetType() != typeof(ListBoxItem)) SelectedItem = null;
Of course, there are many ways to do this, and you'll have to fill in the few blank spots that I left, but you should get the idea.
EDIT >>>
The generic GetParentOfType method is a custom Extension Method that is defined in a separate class named DependencyObjectExtensions:
public static class DependencyObjectExtensions
{
public static T GetParentOfType<T>(this DependencyObject element)
where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject)
parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type))
return parent as T;
return GetParentOfType<T>(parent);
}
...
}
For The each item in the listbox can be selected (only one at a time).
You can come up with one of followings
1- Disable the item after it is selected.
2- Maintain a list at backend to mark each index selectable or unselectable.
To assure that only one item is selected put this in the listbox:
SelectionMode="Single"
then for the unselect when clicking somewhere, you can try to check this events
PreviewMouseLeftButtonUp
LostFocus()
Regards,
I was searching for a solution to this problem, but to prevent the last item in the listbox from becoming selected when clicking on the blank space. my problem is slightly different but has the same solution that I have come up with which works for me.
Although I am using powershell and not c#, I am still utilizing the windows forms listbox control so I think the idea will be applicable.
Also, I couldn't find any discussions of this problem specifically dealing with powershell when I was searching for a solution, so I wound up here.
so I created a variable, maxY, and multiplied the number of list items by the itemheight.
next, at the beginning of the mouse_up event, I just check if the Y location from the mouse click is less than the maxY variable. if true, select the item and run your code, if not, do nothing.
I can only provide a code sample in powershell, but I think the idea is portrayed.
$listbox.add_MouseUP({
$maxY = $this.items.count * $this.itemHeight
if ($_.y -le $maxY) {
$this.SelectedIndex = $this.IndexFromPoint($_.X, $_.y)
#do stuff here
}
else {
$this.clearselection()
}
}
This will clear all selections if clicking on blank space, but will also prevent an item from being selected when clicking on blank space.
I have a MultiView and a Menu in my ASP.NET page - each menu item has a Value property which corresponds to the ViewIndex of the tab to show.
I will sometimes need to set the active view programatically, which works fine for the MultiView, but setting the Selected property of the Menu control is a bit more difficult. I could loop through each item til the value matches the view index I want to show, but this seems a bit hacky.
Any ideas?
I recommend using the MultiView's OnActiveViewChanged event to select the menu item.
protected void myMultiView_ActiveViewChanged(object sender, EventArgs e)
{
int index = ((MultiView)sender).ActiveViewIndex;
myMenu.FindItem(index.ToString()).Selected = true;
}
This way, whenever the view is changed (via SetActiveView(), or otherwise), the menu selection will remain synced. Note that you may also need to set the active view on the OnMenuItemClick event of the menu.