C#/WPF: Why is tab not focusing properly - c#

I have a tab control
<TabControl Height="Auto" Grid.Row="1" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True">
That is bound to Tabs in the ViewModel. I also used CollectionViewSource to focus tabs
protected ObservableCollection<TabViewModel> _tabs;
protected ICollectionView _tabsViewSource;
public ObservableCollection<TabViewModel> Tabs
{
get { return _tabs; }
}
public void OnTabsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
foreach (TabViewModel tab in e.NewItems)
{
tab.CloseRequested += OnCloseRequested;
_tabsViewSource.MoveCurrentTo(tab); // focus newly created tab
}
if (e.OldItems != null && e.OldItems.Count > 0)
foreach (TabViewModel tab in e.OldItems)
tab.CloseRequested -= OnCloseRequested;
}
When I have more that 1 tab, when I create new Tabs, tabs are focused properly
when there are no tabs, new tabs don't seem to be focused properly. notice the tab header
how might I fix this? or what is causing this behavior? the text box (tab content) is shown but the header don't render like its selected
UPDATE
It works with a fresh file/project ... hmm ... must be some related code ... I might redo that part ...

IsSynchronizedWithCurrentItem="True" has no meaning unless you bind your TabControl.ItemsSource to an ICollectionView.
I can't tell if changing your binding from ObservableCollection to ICollectionView will solve your problem, but that is how I have setup my databound tabcontrol.
An alternative could be to expose a new property
public TabViewModel CurrentTabViewModel
{
get
{
return _tabs.CurrentItem as TabViewModel:
}
set
{
_tabs.MoveCurrentTo(value);
}
}
and bind TabControl's SelectedItem to CurrentTabViewModel
<TabControl SelectedItem="{Binding Path=CurrentTabViewModel}" ... />

Without the code, that initializes the single-tab-collection, it's just guessing.
A Workaround for you would be setting SelectedIndex of the tabView = 0 -> first tab is selected initially.
<TabControl Height="Auto"
Grid.Row="1"
ItemsSource="{Binding Tabs}"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="0">

Related

Cannot remove controls after navigating back to page (Win 8 app)

i have a tablet app (windows 8 app) with a starting homepage.xaml . There i have ListView binded to a ObservableCollection with UpdateSourceTrigger set to PropertyChanged ... in this list i create dynamically Panels(with delete button). When i create the button and then i click the delete button everything works fine .. the panel dissapears with nice animation. However, when i create a new panel.. then i navigate to a different page and go back to homepage, click delete to any of those panels they dont dissapear .. But if navigate again to another page and then i go back. The panel is removed. I tried to debug that collection everything looks fine. Tried to change UpdateSourceTrigger to Explicit and call it manually but no success. Thanks for help
ListView Xaml in HomePage.xaml
<ListView x:Name="CListView" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="40, 0, 50, 50"
SelectionMode="None" IsItemClickEnabled="True" ItemClick="CListView_OnItemClick"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollMode="Auto"
ScrollViewer.VerticalScrollMode="Disabled"
ScrollViewer.ZoomMode="Disabled"
ScrollViewer.IsHorizontalScrollChainingEnabled="True"
ItemsSource="{Binding Path=ClControls, ElementName=HomePageElementName, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemContainerStyle>
HomePage.xaml.cs
OnItemRemove
public ObservableCollection<IClControl> ClControls
{
get { return _clEvents; }
set
{
_clEvents = value;
OnPropertyChanged();
}
}
private async void ClControl_OnItemRemove(object sender, ClControlRemoveItemArgs e)
{
var control = sender as ClControl;
if (control == null)
return;
var clEvent = e.ClEventInfo;
// control.OnItemRemove -= ClControl_OnItemRemove;
if (ClControls.Contains(control))
{
ClControls.Remove(control);
}
if (SessionHolder.Instance.ClControls.Contains(control))
{
SessionHolder.Instance.ClControls.Remove(control);
}
await clEvent.Delete();
}

WPF - Elements inside DataTemplate property issue when no binding?

I have the following TabControl:
<TabControl ItemsSource="{Binding Tabs"}>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type vm:TabVM}">
<TextBox></TextBox>
<TextBox Text="{Binding SomeProperty}"></TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The unexpected behaviour is that first TextBox has Text property shared between all tabitems, while second TextBox effectively bind to ViewModel property.
My need is to make independent the first TextBox too, even without binding.
What can I do ?
** UPDATE **
After several tries I've decided to use the ikriv's TabContent.cs.
The only issue I've found with this is that calling the TabControl.Items.Refresh() (i.e. after removing a tabItem) cause the reset of the internal cache.
An unelegant but effective solution may be this:
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
/* CUSTOM */
var view = CollectionViewSource.GetDefaultView(((TabControl)_tabControl).Items);
view.CollectionChanged += View_CollectionChanged;
}
/*
* This fix the internal cache content when calling items->Refresh() method
* */
private void View_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
/* Retrieve all tabitems cache and store to a temp list */
IList<ContentControl> cachedContents = new List<ContentControl>();
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
cachedContents.Add(cachedContent);
}
/* rebuild the view */
_tabControl.Items.Refresh();
/* Retrieve all cached content and store to the tabitems */
int idx = 0;
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
TabContent.SetInternalCachedContent(tabItem, cachedContents[idx++]);
}
}
}
You should use data binding since the same ContentTemplate will be applied for all items in your ItemsSource. Only the binding will be refreshed when you switch tabs basically. The TextBox isn't re-created nor reset.
What can I do ?
You could work around this in the view by handling the SelectionChanged event of the TabControl and reset the TextBox control yourself:
private void tabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = sender as TabControl;
ContentPresenter cp = tc.Template.FindName("PART_SelectedContentHost", tc) as ContentPresenter;
if(cp != null && VisualTreeHelper.GetChildrenCount(cp) > 0)
{
ContentPresenter cpp = VisualTreeHelper.GetChild(cp, 0) as ContentPresenter;
if(cpp != null)
{
TextBox textBox = cpp.FindName("txt") as TextBox;
if (textBox != null)
textBox.Text = string.Empty;
}
}
}
<TabControl x:Name="tabs" ItemsSource="{Binding Tabs}" SelectionChanged="tabs_SelectionChanged">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<StackPanel>
<TextBox x:Name="txt"></TextBox>
</StackPanel>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If you want to persist the text in the TextBox when you switch tabs you could use the attached behaviour from the following article and set its IsCached property to true: https://www.codeproject.com/articles/460989/wpf-tabcontrol-turning-off-tab-virtualization
<TabControl ItemsSource="{Binding Items}" behaviors:TabContent.IsCached="True">
<!-- Make sure that you don't set the TabControl's ContentTemplate property but the custom one here-->
<behaviors:TabContent.Template>
<DataTemplate>
<StackPanel>
<TextBox />
</StackPanel>
</DataTemplate>
</behaviors:TabContent.Template>
</TabControl>
Yet another approach would be to modify the ControlTemplate of the TabControl to include a ListBox as suggested by 'gekka' in the following thread on the MSDN forums: https://social.msdn.microsoft.com/Forums/en-US/4b71a43a-26f5-4fef-8dc5-55409262298e/using-uielements-on-datatemplate?forum=wpf

How do I get the child controls of a ListBox?

I have a ListBox in xaml which has a sub-ListBox inside the top-level ListBox's item template. Because the sub-ListBox is multi-select, and I can't bind the SelectedItems of the sub-ListBox to a viewmodel property for some reason, I'm trying to do a lot of this in the view code-behind.
I have everything working, except for one snag: I want to select all items in each sub-ListBox by default. Since the SelectedItems aren't data-bound, I'm trying to do it manually in code whenever a SelectionChanged event fires on the top-level ListBox. The problem is that I don't know how to get from the top-level ListBox to the sub-ListBox of the top-level selected item. I think I need to use the visual tree, but I don't know how to even get the dependency object that corresponds to the selected item.
Here's the code:
<ListBox ItemsSource="{Binding Path=Stuff}" SelectionChanged="StuffListBox_SelectionChanged" SelectedItem="{Binding Path=SelectedStuff, Mode=TwoWay}" telerik:RadDockPanel.Dock="Bottom">
<ListBox.ItemTemplate>
<DataTemplate>
<telerik:RadDockPanel>
<TextBlock Text="{Binding Path=Name}" telerik:RadDockPanel.Dock="Top" />
<ListBox ItemsSource="{Binding Path=SubStuff}" SelectionMode="Multiple" SelectionChanged="SubStuffListBox_SelectionChanged" Visibility="{Binding Converter={StaticResource StuffToSubStuffVisibilityConverter}}" telerik:RadDockPanel.Dock="Bottom">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</telerik:RadDockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The converter ensures that only the selected top-level item has a visible sub-ListBox, and that's working.
I need to implement the following method:
private void StuffListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox stuffListBox = (ListBox)sender;
foreach (Stuff stuff in e.AddedItems)
{
...
subStuffListBox.SelectAll();
}
}
I tried doing stuffListBox.ItemContainerGenerator.ContainerFromItem(stuff), but that always returns null. Even stuffListBox.ItemContainerGenerator.ContainerFromIndex(0) always returns null.
I also get strange behaviour from the selection changed method. 'e.AddedItems will contain items, but stuffListBox.SelectedItem is always null. Am I missing something?
From what I've read, my problem is coming from the fact that the containers haven't been generated at the time that I'm getting a selection change event. I've seen workarounds that involve listening for a the item container generator's status changed event, but I'm working in Silverlight and don't have access to that event. Is what I'm doing just not possible in Silverlight due to the oversight of making SelectedItems on a ListBox read-only?
Like you say this is probably best done in the ViewModel but you can select all the sub list items in code behind using VisualTreeHelper.
private void StuffListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var stuffListBox = (ListBox)sender;
ListBoxItem item = (ListBoxItem)stuffListBox.ContainerFromItem(stuffListBox.SelectedItem);
ListBox sublist = FindVisualChild<ListBox>(item);
sublist.SelectAll();
}
FindVisualChild Method as per MSDN
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}

How to set focus to a bound ListboxItem by pressing its child element?

I am developping a small WPF application which consist mostly in displaying ObservableCollection<> in others ObservableCollection<>, and so on.
Here is a code example of what my application looks like:
<Listbox Name="MainList" ItemsSource={Binding}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Textblock Text={Binding MainName} />
<Button>Add item</Button>
<Button>Delete item</Button>
<Listbox Name="ChildList" ItemsSource="{Binding Path=ChildItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text={Binding ChildName} />
</DataTemplate>
</ListBox.ItemTemplate>
</Listbox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</Listbox>
And visually it pretty much looks like this:
EDIT:
I will re-explain what I am trying to do.
Whenever I click Button A or Button B I want to Select the MainList ListBoxItem in which they are contained (i.e: A Item)
And in a second time whenever I click Button B:
I want to be sure that a ListBoxItem is selected in ChildList(Second Listbox in the picture)
And if so, I want to delete it in code-behind.
But my main problem is since everything is generated by my bindings I cannot get, so far, an element from my ChildList because ChildList is duplicated in any of my MainList ListBoxItem.
If I understand well the problem is that you want first click on a button of unselected item to select the MainItem, and on next click, when MainItem is already selected, preform click action. Try this when button is clicked:
private ListBoxItem FindItemContainer(DependencyObject obj)
{
while (obj != null && !(obj is ListBoxItem))
{
obj = VisualTreeHelper.GetParent(obj);
}
if (obj != null)
return obj as ListBoxItem;
else
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var lbi = FindItemContainer(sender as DependencyObject);
if (lbi != null)
{
if (lbi.IsSelected)
{
//do click event
}
else
lbi.IsSelected = true;
}
}
Of course you can also do it more MVVM way by binding ListBoxItem.IsSelected to lets say bool MainItem.MyItemIsSelected
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=MyItemIsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
and Button.Command to your ICommand MainItem.DeleteCommand and then when command is executed do something like that:
if (MyItemIsSelected)
{
//do command body
}
else
MyItemIsSelected = true;
which will be better long term because you could replicate SelectedItem behaviour in ChildList object (add MyItemIsSelected and bind it to inner 'ListBoxItem.IsSelected, like discribed above) and add MySelectedItem property to ChildList:
ChildItem MySelectedItem
{
get
{
return Items.FirstOrDefault(n=>n.MyItemIsSelected);
}
}
and your delete command would look like this:
if (MyItemIsSelected)
{
ChildItem selItem = ChildItems.MySelectedItem;
if (selItem != null) ChildItems.Items.Remove(selItem);
}
else
MyItemIsSelected = true;
if everything is data bound and lists are ObservableCollections then you can do all that in object and UI will follow. Actually you can do only this child selection binding bit and still use first solution and in Button_Click look like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var lbi = FindItemContainer(sender as DependencyObject);
if (lbi != null)
{
if (lbi.IsSelected)
{
MainItem mainItem = lbi.Content as MainItem;
ChildItem selChild = mainItem.ChildItems.MySelectedItem;
if (selChild != null) mainItem.ChildItems.Items.Remove(selChild);
}
else
lbi.IsSelected = true;
}
}
Here is simple, working example on Dropbox
You can do everything you want to do in code behind:
Find the item on which the Button is pressed: in the click-event, cast the sender parameter to type Button. Its DataContext property will contain the item you want to select.
Select the item: set MainList.SelectedItem to the item.
Focus will be on the Button, but that should be ok, since it is inside the item.
Find the selected item in second listbox: locating the ListBox in the DataTemplate is tricky, but you could set its IsSynchronizedWithCurrentItem property to True, and then use the underlying child collection's default CollectionView. You'd find the current item of MainList like above. Then you'd use:
itemToDelete = CollectionViewSource.GetDefaultView(item.ChildItems).CurrentItem;
item.ChildItems.Remove(itemToDelete);

LogicalTreeHelper.GetChildren - ObservableCollection Move() causing ContentControl in DataTemplate to lose it's content?

This is very strange. The point of the code below is to support an attachedProperty which will notify a container if any of it's children have received focus.
i.e. I have a Grid with a textBox somewhere in it's Content and I want to turn the Grid Blue if one of those controls gets focus.
I have a ListView with an ItemsTemplate. The ItemsTemplate is a DataTemplate containing a few things...but one of them being a ContentControl.
Example:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The Binding on the ContentControl should display a certain type of UserControl.
Upon creation...works just fine. If I recursively iterate template of the listViewItem starting with the Grid element...it will traverse the ContentControl's "Content" as well.
HOWEVER...once I do a .Move() on the ObservableCollection that the ListView ItemsSource is bound to, the ContentControl.Content is empty according to the LogicalTreeHelper.
What gives?
If I inspect the ContentControl, it SHOWS me the content...but LogicalTreeHelper.GetChildren returns and empty Enumerator.
I'm confused...
Can anyone explain why this would be the case?
LogicalTreeHelper iterator method
public static void applyFocusNotificationToChildren(DependencyObject parent)
{
var children = LogicalTreeHelper.GetChildren(parent);
foreach (var child in children)
{
var frameworkElement = child as FrameworkElement;
if (frameworkElement == null)
continue;
Type frameworkType = frameworkElement.GetType();
if (frameworkType == typeof(TextBox) || frameworkType == typeof(ListView) ||
frameworkType == typeof(ListBox) || frameworkType == typeof(ItemsControl) ||
frameworkType == typeof(ComboBox) || frameworkType == typeof(CheckBox))
{
frameworkElement.GotFocus -= frameworkElement_GotFocus;
frameworkElement.GotFocus += frameworkElement_GotFocus;
frameworkElement.LostFocus -= frameworkElement_LostFocus;
frameworkElement.LostFocus += frameworkElement_LostFocus;
// If the child's name is set for search
}
applyFocusNotificationToChildren(child as DependencyObject);
}
}
Aloha,
Here is an suggetion how you could solve your problem:
I am not sure if i spelled right the GotFocus event but its a RoutedEvent and you can use it anywhere in your visual tree.
If one of your items receives a focus your ListView will get notified and inside the handler you can do whatever you want.
How about this:
<ListView GotFocus="OnGotFocus">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<ContentControl Content="{Binding Something}"/>
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is just some random logic to demonstrate what you can do.
public void OnGotFocus(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if(((MyViewModel)item.Content).SomeColor == "Blue")
{
Grid g = VisualTreeHelper.GetChild(item, 0) as Grid;
g.Background = Colors.Blue;
}
}
GotFocus is a RoutedEvent which will bubble up the visual tree if fired. So catch the event somewhere and inspect which was the original source object that fired the event. Or inspect what was the ViewModel propery of the object which fired the event.

Categories

Resources