I have a situation where I am trying to set the tab order (tabindex) for controls that are loaded dynamically. The main XAML looks like this:
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding ItemsDataSource}" Name="overlayItems" ItemTemplate="{StaticResource DetailsTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
For example purposes, assume the DetailsTemplate is something simple like this:
<DataTemplate x:Key="DetailsTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="22" />
<RowDefinition Height="22" />
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Padding="0">Field 1</Label>
<TextBox Grid.Column="1" Grid.Row="0" Name="field1TextBox" TabIndex="0" Text="{Binding Field1Value}"/>
<Label Grid.Column="0" Grid.Row="1" Padding="0">Field 2</Label>
<TextBox Grid.Column="1" Grid.Row="1" Name="field2TextBox" TabIndex="1" Text="{Binding Field2Value}"/>
<Label Grid.Column="0" Grid.Row="2" Padding="0">Field 3</Label>
<TextBox Grid.Column="1" Grid.Row="2" Name="field3TextBox" TabIndex="2" Text="{Binding Field3Value}"/>
</Grid>
</DataTemplate>
This XAML works just fine except for the resulting tab order.
Assuming that the ItemsDataSource is a collection of a class and contains 3 instances of that class, three sets of the DetailsTemplate data template are created. However the tab order does not change, every field1TextBox remains at TabIndex 0. This means, instead of tabbing from the first instances of field1TextBox, to field2TextBox, to field3TextBox, the tab goes from the first instance of field1TextBox to the second instance of field1TextBox then to the third instance of field1TextBox then to the first instance of field2TextBox, and so on. My question is, how do I get the tab order corrected where, say, the second instance of the data template would have its text boxes tab indexes updated to 3, 4 and 5 respectively?
You'll find the answer in the KeyboardNavigation.TabNavigation Attached Property page from MSDN. This property Gets or sets the logical tab navigation behavior for the children of the element that this property is set on.
There are several possible values in the KeyboardNavigationMode Enumeration used that affect the tabbing order in different ways, but you're after the Local value, which has the effect that Tab Indexes are considered on local subtree only inside this container and ... [Navigation leaves the containing element when an edge is reached].
<Grid KeyboardNavigation.TabNavigation="Local">
...
</Grid>
Related
Referencing this example:
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-bind-to-a-collection-and-display-information-based-on-selection
(some relevant code snippets:)
<Window.Resources>
<local:People x:Key="MyFriends"></local:People>
<DataTemplate x:Key="DetailTemplate">
<Border Width="300" Height="100" Margin="20"
BorderBrush="Aqua" BorderThickness="1" Padding="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="First Name:"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=FirstName}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Last Name:"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=LastName}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Home Town:"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=HomeTown}"/>
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<ListBox Width="200" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource MyFriends}}"/>
<ContentControl x:Name="contentControl1"
Content="{Binding Source={StaticResource MyFriends}}"
ContentTemplate="{StaticResource DetailTemplate}" />
Both ListBox.ItemsSource and ContentControl.Content bind to the same source (MyFriends, an instance of the People class which derives from ObservableCollection<Person>). If my understanding is correct, this means that both the ListBox.ItemsSource and ContentControl.Content properties will be bound to the same implicitly created instance of ListCollectionView.
I understand that setting ListBox.IsSynchronizedWithCurrentItem="True" synchronizes ListBox.SelectedItem and ItemCollection.CurrentItem.
DetailTemplate (above) displays the details of the selected ListBox item, despite being 'bound' to a ListCollectionView. Specifying Path=/ (what I thought would be necessary to achieve the resulting behavior) does not have any effect - it's as if WPF knows to do it implicitly somehow:
<ContentControl x:Name="contentControl1"
Content="{Binding Source={StaticResource MyFriends}, Path=/}"
ContentTemplate="{StaticResource DetailTemplate}" />
As a test, I created another ContentControl with Content bound to a DataTemplate containing a ListBox:
<ContentControl x:Name="contentControl2"
Content="{Binding Source={StaticResource MyFriends}}"
ContentTemplate="{StaticResource DetailTemplate2}" />
<DataTemplate x:Key="DetailTemplate2">
<ListBox ItemsSource="{Binding}"></>
</DataTemplate>
And it displayed the list.
My question is: Why does DataTemplate get the selected Person object while the ListBox and DetailTemplate2 get the People collection?
(the behavior is desirable, I just don't understand what black magic is occurring under the hood to make it so)
Is a good question! I didn't notice that until read your post. So, after did some digging from source code of PropertyPathWorker, it appears that when PropertyPathWorker failed to solve a member of an object, in your case, it try to solve 'FirstName', 'LastName' ect. with 'MyFriends', it will try to solve it with the view of the object. And if still failed, it will try to solve it with view's CurrentItem, and that's where the magic happened. You can find those codes in PropertyPathWorker.UpdateSourceValueState(int k, ICollectionView collectionView, object newValue, bool isASubPropertyChange) and PropertyPathWorker.ReplaceItem(int k, object newO, object parent).
In a windows 8.1 project i have a ListView that displays several items that look something like this:
I basically display agenda points, that can have 2 sub levels
if subpoint at first level has no subpoints itself it is a radiobutton, otherwise the subpoints it contains are radiobuttons.
the radiobutton points have this template.
<DataTemplate x:Key="WithSubTemplate2">
<Grid Width="280" Height="50" Margin="85,0,0,0" HorizontalAlignment="Right">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="5" />
</Grid.RowDefinitions>
<RadioButton GroupName="meetingFiles" Tag="{Binding}" Checked="RadioButton_Checked" Content="{Binding Name}" Style="{StaticResource RadioButtonStyle1}"></RadioButton>
<Ellipse Width="20" Height="20" Fill="#b3d0dd" HorizontalAlignment="Right" Margin="0,0,10,0"></Ellipse>
<TextBlock Text="{Binding AttachmentNumber}" HorizontalAlignment="Right" FontFamily="Segoe UI Regular" FontSize="16" Foreground="{StaticResource BrandBrush}" Margin="0, 14,15,0"></TextBlock>
<Grid x:Name="whiteLine" Grid.Row="1" Width="270" Height="1" Background="#80b0c6" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
When i check one of the radio buttons, i have a control that displays a pdf, and then when i want to edit that pdf it navigates to another page.
What i want is, when i go back to the previous page to have the RadioButton i checked earlier to be checked when the page opens.
Any way i can achieve this?
You can simply use:
this.NavigationCacheMode = NavigationCacheMode.Required;
That'll save current page status :).
I am fairly new to WPF and MVVM and a newb in general, so thank you in advance for your patience.
I am using a custom class in my model, and I have an ObservableCollection of that custom object in my viewmodel. In the class' constructor, I am adding the object to the collection when it is instantiated. In my view, I am using a DataGrid that is bound to the collection to list all active instances of the class. I am trying to implement a drag-and-drop from the DataGrid onto a trash can icon that would allow a user to dispose of unneeded instances of the class.
The problem is that when you click anything in the DataGrid, the program immediately crashes with an ArgumentOutOfRange exception - ("The given DisplayIndex is out of range. DisplayIndex must be greater than or equal to 0 and less than Columns.Count." "Actual value was 0"). DisplayIndex seems to relate to the DataGrid column, so this exception is probably due to the fact that I am not displaying any columns in the traditional sense - in my DataGrid, AutoGenerateColumns is set to False, and I am displaying everything I need to display using a RowDetailsTemplate. (The reason for this is that the area where I am displaying the DataGrid is narrow, so I need a nested, item-specific grid to represent the item properly.) The DataGrid displays and syncs with the collection fine, but obviously has some issues. I have read dozens of links on DataGrid crashes, and haven't found anything involving this exception.
My desired behavior is to pass the custom object represented by the DataGrid item to a target when I drag and drop it. I don't care which "column" they clicked or anything else - I just need a way to pass either an object reference or a SelectedIndex (the items index in the collection) to a method in the viewmodel.
Thank you in advance for any help! The offending bit of code (XAML) seems to be:
<ScrollViewer DockPanel.Dock="Bottom" Margin="2" Width="180" ScrollViewer.VerticalScrollBarVisibility="Auto">
<DataGrid ItemsSource="{Binding Path=myCollection, Mode=OneWay}" AutoGenerateColumns="False" RowDetailsVisibilityMode="Visible" HeadersVisibility="None">
<DataGrid.RowDetailsTemplate>
<DataTemplate DataType="model:myClass">
<Border CornerRadius="10" Background="AliceBlue">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding MyString1}" FontSize="21" VerticalAlignment="Bottom" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MyCustomProperty, Converter={StaticResource MyIValueConverter}}" VerticalAlignment="Bottom" />
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding MyString2}" TextWrapping="Wrap" />
<Image Source="/Resources/image1.png" Grid.Column="2" Grid.Row="0">
<Image.DataContext>
<Properties:Resources/>
</Image.DataContext>
</Image>
<Image Source="/Resources/image2.png" Grid.Column="2" Grid.Row="1">
<Image.DataContext>
<Properties:Resources/>
</Image.DataContext>
</Image>
</Grid>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</ScrollViewer>
The issue was indeed because I am not generating any "traditional" columns. This is apparently a known bug, documented here: Microsoft Bug Report
As a workaround, I just defined an invisible column within the DataGrid and it seems to behave properly now:
<DataGrid.Columns>
<DataGridTemplateColumn Visibility="Hidden" />
</DataGrid.Columns>
I want to create a debug window that will allow us to edit properties from various objects in our app while it runs. This will allow us, for example, to tweak threshold values for certain heuristic rules in our app, without requiring a rebuild and/or app restart.
The goal is to tell the debug window to enable editing some property of an object. The window then obtains the property's value, keeps a weak reference to the object and displays an appropriate data template (based on the value's type) to enable us to edit the value and apply the new value to the object when needed.
Problem:
The data templates are applied correctly and the value is displayed for each debug item in a TextBox. However, the Value property of each DebugItem, to which the TextBox is bound, is NEVER updated. I have set a breakpoint on that property's setter; the breakpoint is never triggered.
Here is my current setup:
I have a DebugItems collection of DebugItem objects in my view model.
Each DebugItem has a Value property of type object.
For debugging purposes, the Value property always contains a string.
I have created a data template for the DebugItem type and the System:String type.
My window contains a ListBox that is bound to the DebugItems collection and displays DebugItems using the data template defined above, in a ContentPresenter. A TextBox inside that data template is also bound to the Value so that it enables us to edit the string value using the other, System:String data template defined above *.
* I am under the impression that this has to do with why the edit doesn't work. I could be mistaken, through.
Relevant part of the window:
<Grid Background="#CECECE">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding DebugItems}" Background="Transparent" BorderBrush="Transparent" />
</ScrollViewer>
</Grid>
My data template:
(Of particular interest are the inner ContentPresenter and its embedded System:String data template.)
<DataTemplate DataType="{x:Type Debug:DebugItem}">
<Grid Height="60" d:DesignWidth="403.06">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition Width="181*" />
<ColumnDefinition Width="110" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Label}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="14,0,0,0" Foreground="Black" FontWeight="Bold" />
<ContentPresenter VerticalAlignment="Center" Grid.Column="1" Content="{Binding Value}" Height="Auto">
<ContentPresenter.Resources>
<!-- String -->
<DataTemplate DataType="{x:Type System:String}">
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<UniformGrid Grid.Column="2" Rows="1">
<Button Margin="8,8,0.5,8" Command="{Binding UpdateCommand}" Style="{DynamicResource ButtonStyle}" Content="Update" />
<Button Margin="4.5,8,8,8" Command="{Binding ApplyCommand}" Style="{DynamicResource ButtonStyle}" Content="Apply" />
</UniformGrid>
</Grid>
</Grid>
</DataTemplate>
Any ideas?
Okay, so the situation as like this:
I've got an ItemsControl, which contains several children.
the children are actually a UserControl, this is it's Xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--DAY HEADER-->
<Border x:Name="dayHeader" Height="20" BorderBrush="#B0B6BE" BorderThickness="1" Grid.Row="0" Background="{StaticResource WeekHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" TextAlignment="Center"
TextWrapping="NoWrap" Margin="1.5,0,0,0" Text="18"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"
TextWrapping="NoWrap" Margin="2,0,0,0" Text="Thuesday" />
</Grid>
</Border>
<!--DAY HOURS-->
<ItemsControl x:Name="dayHours" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="dayHourBorder" Height="30" BorderBrush="#B0B6BE" Width="193" Tag="{Binding Index}" BorderThickness="1,0,1,1" Background="AliceBlue"
MouseLeftButtonDown="dayHourBorder_MouseLeftButtonDown" MouseLeftButtonUp="dayHourBorder_MouseLeftButtonUp"
MouseMove="dayHourBorder_MouseMove" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
IN SHORT
it's a grid that in the first row has a border
and in the second row has an ItemsControl.
Alright now... what i wanna do is, whenever i click between the child ItemControls (day hours) i want them to execute some function on the LostFocus() event and on GotFocus() event.
problem is... they don't fire! and it tried registering to them from every possible angle!
HALP.
UPDATE
I tried executing Focus() on MouseLeftButtonDown, but what happened is, it went straight to OnLostFocus, which is not what i want...
i don't understand it
Here is an overview on focus in Silverlight. The article mentions four conditions that need to be satisfied in order for the control to get focus. You should check those four conditions for your control and it should be fine I suppose.
You should also consider on which element you'd like to receive those events as GotFocus and LostFocus are bubbling events.
I've managed to fix this issue by doing this:
doing: this.Focus();
and then: e.Handled = true;
the problem was that the ItemControl usually can't hold focus, and so the click event bubbles up.
but when i tell him it's Handled, it stops it's bubbling and won't lose the focus.