Keyboard navigation in WPF Grids - wpf

Suppose you have a StackPanel, which contains a ScrollViewer which contains another StackPanel with an ItemsControl with a bound ItemsSource. This ItemsSource is bound to a collection of Grids created at runtime. Each Grid contains a label and a textbox/combobox/a few checkboxes that all have a unique TabIndex value within the StackPanel.
Here is the xaml:
<ScrollViewer Name="scrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel Name="stackPanel" MinWidth="500" Width="Auto">
<ItemsControl Name="itemsControl" ItemsSource="{Binding ElementName=SomeWindow, Path=GridsCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</ScrollViewer>
I want to simply tab from one control to the next, but only within the controls in the grids in the grids collection. So far I've tried different KeyboardNavigation.TabNavigation settings but without any luck. What is the best way to do this?

Set TabNavigation to KeyboardNavigationMode.Cycle for each container you want to behave like that, so the focus won't escape it as long as you use Tab and Shift+Tab:
KeyboardNavigation.SetTabNavigation(grid1, KeyboardNavigationMode.Cycle);
If you want to change Ctrl+Tab behaviour, use KeyboardNavigation.SetControlTabNavigation.

You can apply an implicit style that disables tabbing for every Control, then you re-enable it for just what you want to be tab-able:
<ScrollViewer Name="scrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ScrollViewer.Resources>
<Style TargetType="Control">
<Setter Property="IsTabStop" Value="False" />
</Style>
</ScrollViewer.Resources>
<StackPanel Name="stackPanel" MinWidth="500" Width="Auto">
<ItemsControl Name="itemsControl"
ItemsSource="{Binding ElementName=SomeWindow, Path=GridsCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</ScrollViewer>
Don't forget to set IsTabStop on your dynamically generated Grids to True

Related

Map control within Listbox

I am trying to display multiple maps within an Listbox.
<Grid Name="MainGrid">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" >
<WrapPanel Name="wrap" >
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Border Margin="5" MinWidth="500" MinHeight="400" BorderThickness="2" BorderBrush="Black"
Width="200"
Height="200" >
<esri:MapView MouseDown="MapView_MouseDown" MouseUp="MapView_MouseUp" >
<esri:Map >
<esri:ArcGISTiledMapServiceLayer ID="BaseMap" ServiceUri="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
</esri:Map>
</esri:MapView>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
</ScrollViewer>
</Grid>
The map does not get displayed. Only the logo "esri" gets displayed. But if I remove the listbox, it works fine. What could be the issue?
I am very sure that there are items in my listbox otherwise "esri" would not have appeared.
I have tried Itemscontrol as well but its the same result.
In WPF, we don't put UI elements into collections, we put data objects into collections. Each data item should contain whichever properties are needed to data bind to the UI control that you actually want to display. So your MyList collection should contain a collection of data objects.
Once you have created a custom class to contain these required properties, you will then need to declare a DataTemplate that defines which UI element(s) to display. (See the Data Templating Overview page on MSDN for further information on this).
<ControlTemplate x:Key="YourCustomControlTemplate">
<!-- Define your UI content here -->
</ControlTemplate>
Once you have declared your custom ControlTemplate for your custom data class, you should then put it into a Style for your data object:
<Style TargetType="{x:Type YourXamlPrefix:YourCustomDataClass}">
<Setter Property="Template" Value="{StaticResource YourCustomControlTemplate}" />
</Style>
Note that I have omitted the x:Key directive on this Style... you can add one, but without one (and as long as this Style has been declared in scope of the UI), the Style will be automatically applied to your data objects in the collection.

Is there a way to apply a scrollviewer to only part of a treeview item?

I have a treeview where Each treeview item contains both a signal and a signalname.
I want to have a namepanel inside the treeview item that expands, but i want a scrollviewer to only scroll across the right portion of the panel that contains the signals of the treeviewitem.
Is the only answer to create a custom treeview class? My original idea was to apply two style templates to each of the treeviews so that the expander button is just on one treeview and just make the expansion of one result in the expansion of the other. but ultimately I couldn't figure out a a way to link the items in the two treeviews using triggers so that when one item expands, its counterpart expands.
i also played around with customizing it using just the controltemplate, but I feel like the scrollviewer needs to be placed around only part of the treeviewitem, but there is no way to split the item going to itemspresenter while inside the treeview control template.
Is my best option to just create a custom treeview?
edit:
creating an itemtemplate like suggested will create a separate scrollviewer for part of each item, so I would end up with 6 small scrollviewers. But what I'm trying to do is apply a scrollviewer over the right column of all the items. However, to apply a scrollviewer to all items, I think I would need to place it within the treeview controltemplate. But in the treeviewcontroltemplate you only have access to itemspresenter:
<ControlTemplate TargetType="TreeView">
<ScrollViewer
Focusable="False"
CanContentScroll="False"
Padding="4">
<StackPanel>
<wpfExp:SignalGraphAxis
PenColor="{Binding ElementName=GraphColorPicker, Path=SelectedColor, Mode=OneWay}"
Height="{Binding ElementName=graph_viewer, Path=GraphHeight, Mode=OneWay}"
PenWidth="{Binding ElementName=graph_viewer, Path=GraphPenWidth, Mode=OneWay}"
X_Scale="{Binding ElementName=graph_viewer, Path=X_Scale, Mode=OneWay}"
MaxTimeValue="{Binding ElementName=graph_viewer, Path=_SignalDataViewModel.MaxTimeValue, Mode=OneWay}"
/>
<ItemsPresenter />
</StackPanel>
</ScrollViewer>
</ControlTemplate>
You can do it using Itemtemplate. here is an example
<TreeView>
<TreeView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0">
<!-- Your Signal data binding -->
</ScrollViewer>
<!-- Your Signalname data binding -->
</Grid>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Prevent WPF control from expanding beyond viewable area

I have an ItemsControl in my user control with a scroll viewer around it for when it gets too big (Too big being content is larger than the viewable area of the UserControl). The problem is that the grid that it is all in just keeps expanding so that the scroll viewer never kicks in (unless I specify an exact height for the grid). See code below and thanks in advance.
<UserControl x:Class="BusinessObjectCreationWizard.View.TableSelectionPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<GroupBox FontWeight="Bold" Height="300px"
Header="Tables"
Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal"
ItemsSource="{Binding Path=AvailableTables}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=DisplayName}"
IsChecked="{Binding Path=IsSelected}"
Margin="2,3.5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</UserControl>
This user control is loaded here
<Border Background="White" Grid.Column="1" Grid.Row="0">
<HeaderedContentControl Content="{Binding Path=CurrentPage}"
Header="{Binding Path=CurrentPage.DisplayName}" />
</Border>
I would like to not specify the height.
If you remove the Height from your GroupBox (which, as far as I understand, is what you want to do), then it will fill its container, unless there's a panel upstream that imposes its own sizing rules.
I used this simplified version of your XAML. I removed the template and the binding, and hard-coded some items, to make this stand alone; those changes won't affect the way layout is done.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</Window>
Run it, and you'll see that the content does indeed size to fit the window, and the scrollbar only enables when the window gets too small to see all three items. I believe this is what you want.
So the problem is most likely one of the parent panels, one you're not showing in your sample XAML. The problem you describe could occur if your GroupBox appears inside a StackPanel:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<StackPanel>
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</StackPanel>
</Window>
Now the GroupBox appears at the top of the Window, sized to exactly fit its contents. If you shrink the Window enough, the GroupBox will be cut off -- because it's sized to fit its content, not its container. This sounds like the problem you're describing.
The reason is that StackPanel asks its children what their ideal height is (based on their content), and uses that height. Without StackPanel (or something similar), the default is to respect the control's VerticalAlignment, and if that's set to the default value of Stretch, then the control is stretched to fill its parent. This means it won't be taller than its parent, which sounds like what you want.
Solution: remove the StackPanel (or whatever else is causing you problems) and use something else. Depending on what you're trying to accomplish, you might have better luck with a DockPanel or a Grid. Hard to tell without knowing more about your layout.
Edit: Okay, it looks like the problem is indeed the HeaderedContentControl parent -- but not directly. HeaderedContentControl isn't a panel, so it doesn't do any layout of its own (and its descendant, GroupBox, doesn't have this same problem). The problem is its default template -- which includes a StackPanel. The good news is, you're free to use a different template, let's say one with a DockPanel instead:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<HeaderedContentControl>
<HeaderedContentControl.Style>
<Style TargetType="{x:Type HeaderedContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type HeaderedContentControl}">
<DockPanel>
<ContentPresenter ContentSource="Header" DockPanel.Dock="Top"/>
<ContentPresenter/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</HeaderedContentControl.Style>
<GroupBox FontWeight="Bold" Header="Tables" Padding="2">
<ScrollViewer>
<ItemsControl FontWeight="Normal">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Baz</TextBlock>
</ItemsControl>
</ScrollViewer>
</GroupBox>
</HeaderedContentControl>
</Window>
If you leave off the <HeaderedContentControl.Style> part, this reproduces your problem; but with the style in place, it allows the GroupBox to fill its container, so the ScrollViewer will get a scrollbar when you want it to.
If the previous answer doesn't fix the problem, you could also try binding the Width, Height of your grid to the ActualWidth, ActualHeight of your parent UserControl. Something like:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication.UserControl1"
x:Name="UserControl">
<Grid Height="{Binding ElementName=UserControl, Path=ActualHeight}"
Width="{Binding ElementName=UserControl, Path=ActualWidth}" />
In this case you aren't setting an explicit width and height but you are limiting the Grids width/height to the constraints of the UserControl it sits in.
I had the same issue, after reading this response I replaced all StackPanels with Grids in UserControl. It resolved the Scrollbar issue.
Try removing the grid entirely and setting the HorizontalAlignment and VerticalAlignment directly on the GroupBox. If a layoutpanel has only one child, it's often redundant... this migth be true in your case.
If that doesn't work... what's the parent of your grid control?
Why not just use a listbox instead of an itemscontrol, that has a built in scrollviewer.
They are different. If you do not want to have the items selectable, then don't use a ListBox. It is going to be heavier, and will also have the deselect a selection everytime the user clicks on an entry. Just put the ItemsControl in a ScrollViewer
I had the same problema with ListBox, it wasn't expanding and the scroll viewer didn't appear. I solved it as follows:
<UserControl x:Class="TesteView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid MaxHeight="710">
....
....
<StackPanel>
<ListBox MaxHeight="515"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Path=Teste,Mode=TwoWay}">
....
....
</ListBox>
</StackPanel>
</Grid>
</UserControl>

Customize WPF databinding: How to add custom logic?

i have a question regarding some complex data-binding.
I want to be able to update a grid (which has the property "IsItemsHost" set to true)
dynamically whenever a data-binding occurs.
Actually i am using a CustomControl which is an ItemsControl and this
has the Grid in its ControlTemplate.
To be more specific, i bind the grid to some items and i want to
change the number of grid rows depending on these items,
add something like a header (one row containing some text),
and set the items' Grid.Row and Grid.Column using some custom logic.
What is the easiest way to apply such behaviour
whenever the bound data is updated?
Do i have to use a viewmodel that also contains the header data?
Thanks in advance.
Code of the CustomControl Generic.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TimeTableControl">
<Style TargetType="{x:Type local:TimeTableControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TimeTableControl}">
<Border Width="Auto" Height="Auto" BorderBrush="#FF4B5A9B" BorderThickness="4" CornerRadius="4" Margin="2" Padding="0" Background="White">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Viewbox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DayCaption}"/>
</Viewbox>
<Border Grid.Row="1" BorderThickness="0,2,0,0" BorderBrush="#FF4B5A9B">
<Grid Name="ContentGrid" IsItemsHost="True">
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Grid is used for layout. If you have a changing number of items in some collection, what you really want is ItemsControl, or more specific ListBox (if you want item selection, etc).
If you still want Grid-like behavior of individual rows, you may want to define a Grid in ItemsControl.ItemTemplate and play with Grid.IsSharedSizeScope at ItemsControl level. Alternatively, you may just use ListView instead to get the grid look and item selection in a package.
Update: I got it working by creating a custom panel that uses MeasureOverride and ArrangeOverride to update itself. This lets me adjust the panel to the children and I don't even need to use a grid. This also makes the control lookless.

ListView with nested Expander not collapsing

This question is the same as this other unanswered question.
When the Expander is expanded the outer ListView grows to make space for the expanders contents, but when the Expander is then collapsed the view does not force the ListView to resize.
Reduced code, with notes after:
<!--<StackPanel>-->
<ItemsControl>
<!-- ParameterGroupView -->
<Border BorderBrush="Brown" BorderThickness="1" CornerRadius="4" Padding="4">
<ListView HorizontalContentAlignment="Stretch">
<Expander Header="Expander A" IsExpanded="False">
<ListView HorizontalContentAlignment="Stretch">
<!-- TextView -->
<TextBlock >Content A</TextBlock>
<TextBlock >Content B</TextBlock>
</ListView>
</Expander>
</ListView>
</Border>
</ItemsControl>
<!--</StackPanel>-->
I have the ParameterGroupView in a ItemsControl or StackPanel because there is actually many ParameterGroupView entries. Swapping to a StackPanel does not change the behaviour.
Removing the Boarder does not affect the behaviour, but having it helps show the behaviour with only a single ParameterGroupView.
There can be many Expander sections in the outer ListView, and the Expander can have many entities inside the inner ListView.
The outer ListView and Expander is to replace a TreeView, that was used to have a list of collapsible nodes, but the TreeView's internal use of grids, means the TextView items were squashed horizonatlly, the same as if you remove ether HorizontalContentAlignment="Stretch" attributes.
So if there is another way to wrap/wire all this up, I'll be also happy.
This is a problem because our TextView blocks are large and there are many Expanders.
Edit: TextView is used as the code is data-bound, and thus dynamically put together. So any replacement for ListView would need some form of ItemsSource
Found the solution, and it details where in the question already.
ItemControl accepts an ItemsSource and auto resizes. So replacing the two ListViews with ItemControls gets the nested collapsing.
But there is no scrolling, so wrapping the outer ItemControl with a ScrollViewer, reproduces the complete desired effect.
<ScrollViewer
VerticalScrollBarVisibility="Auto">
<ItemsControl>
<!-- ParameterGroupView -->
<Border
BorderBrush="Brown"
BorderThickness="1"
CornerRadius="4"
Padding="4"
Height="Auto">
<ItemsControl
HorizontalContentAlignment="Stretch">
<Expander
Header="Expander A"
IsExpanded="False">
<ItemsControl
HorizontalContentAlignment="Stretch">
<!-- TextView -->
<TextBlock>Content A</TextBlock>
<TextBlock>Content B</TextBlock>
</ItemsControl>
</Expander>
</ItemsControl>
</Border>
</ItemsControl>
</ScrollViewer>
I was testing with double Expanders in the Border and double Border sections.
The most obvious thing to do here is to put the expanders in a container other than a listview:
<Border BorderBrush="Brown" BorderThickness="1" CornerRadius="4" Padding="4">
<StackPanel>
<Expander Header="Expander A" IsExpanded="False">
<ListView HorizontalContentAlignment="Stretch" MinWidth="100">
<ListBox Name="listb"></ListBox>
<!-- TextView -->
<TextBlock >Content A</TextBlock>
<TextBlock>Content B</TextBlock>
</ListView>
</Expander>
</StackPanel>
</Border>
The container resizes around content just fine.
If you absolutely must have it in a ListView (which is possible) then I don't know of a way to get a ListView to resize easily once it's grown (beyond setting explicit sizes of the whole thing, which is clumsy and not useful). If thats the case then it's possible that you'll have to use a controllable listbox to display all the open expanders, or display the content in a different way (like in a popup, maybe?) if you want to be able to see it all at a glance.

Resources