Block user navigation in a Pivot item for UWP apps - c#

How can you block a user from clicking on a Pivot item in a UWP XAML app but still show the heading for each pivot item? For example, I have three pivot items that are labeled "Step 1", "Step 2", and "Step 3". I want each of those items to show up at the top of the pivot table, but not be user engagable. They are only there to proivde awarness to the users current location in a process.
I have tried IsLocked="true" in the definition of the pivot table, but it only shows me the title for the pivot item I am currently on.

I tried IsEnabled="false" and that didn't work. Then I tried data binding to a property and used the setter to restrict its value, and that did work.
View:
<Pivot SelectedIndex="{x:Bind PageViewModel.MyPivotIndex, Mode=TwoWay}">
<PivotItem Header="Item1">
<TextBlock Text="Stuff1"/>
</PivotItem>
<PivotItem Header="Item2" IsEnabled="False">
<TextBlock Text="Stuff2"/>
</PivotItem>
</Pivot>
ViewModel:
private int _myPivotIndex;
public int MyPivotIndex
{
get
{
return _myPivotIndex;
}
set
{
if (ConditionMet)
{
_myPivotIndex = value;
}
else
{
_myPivotIndex = 0;
OnPropertyChanged("MyPivotIndex");
}
}
}
A non-MVVM option that should work is if you used the "SelectionChanged" event in the code-behind to check a condition and then if need be set it back to Item1 by setting SelectedItem or SelectedIndex.
If you want the "disabled" items to not highlight on mouse-over, you'll have to copy the style (https://msdn.microsoft.com/en-us/library/windows/apps/mt299144.aspx) and modify it in the the "PointerOver" Visual State.

Related

UWP Grid inside Datatemplate

I have a Pivot where I set the header in my Pivot.HeaderTemplate it is basically just showing Names of Books. In my Pivot.ItemTemplate I want to show a Grid which is build in my .xaml.cs but since the Grid is in my DataTemplate I can not access the Grid x:Name anymore in the code behind in .xaml.cs. books is a Collection of Books which contains a Name and a Title
MainPage.xaml
<Pivot ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
x:Name="BooksGrid"
BorderBrush="Black" BorderThickness="1 1 0 0"
Margin="0 10 0 0>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
Now I want to acces BooksGrid iny the code behind and actually create the Grid
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
}
private void DrawGrid()
{
//create columns of Grid
for (int i = 0; i < booksize.XProperties.Count + 1; i++)
{
BooksGrid.ColumnDefinitions.Add(new ColumnDefinition
{
});
}
BooksGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
....
Already here at BooksGrid.ColumnDefinitions.Add(...) I get the error that BooksGrid can not be found.
My DrawGrid works if I do not place the Grid definition in my DataTemplate and also outside myPivot. So the MainPage.xaml.csdoes not find it when the Grid is inside my DataTemplate
I've read that the solution might be that I have to acces the Grid instance that I want to work with, as soon as the DataTemplate gets loaded. But I do not know how to do that either.
EDIT PART to first solution:
I'm also using BooksGrid in another method
MainPage.xaml.cs
private void DrawBooksFront(Front front)
{
int row;
int column;
column = booksize.XProperties.IndexOf(front.CustomProps[booksize.XLabel])+1;
row = booksize.YProperties.IndexOf(front.CustomProps[booksize.YLabel])+1;
Frame newFrame = new Frame();
TaskBoardGrid.Children.Add(newFrame);
Grid.SetColumn(newFrame, column);
Grid.SetRow(newFrame, row);
}
The reason you cannot access your BooksGrid is because it will be dynamically generated for each book in the books collection. So for every book a Grid will be generated.
OPTION 1:
You can add a Loaded event to your grid:
<Pivot x:Name="Pivot" ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
BorderBrush="Black" BorderThickness="1,1,0,0"
Margin="0,10,0,0" Loaded="DrawGrid">
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
and in your code behind:
private void DrawGrid(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
// Load your grid..
}
EDIT - OPTION 2:
If you'd like to access your grids from code behind in a different way (like suggested in your edit) you can always do the following:
private void DrawBooksFront(Front front)
{
// Loop through the pivot's items and get the content from each item's ContentTemplate.
foreach (var item in Pivot.Items)
{
PivotItem pivotItem = Pivot.ContainerFromItem(item) as PivotItem;
Grid grid = pivotItem.ContentTemplate.LoadContent() as Grid;
// Do something with the grid.
}
}
If your goal is to display previews of the pages of the book inside a PivotItem in a grid-like manner [picture below], then you're better off placing GridView in a DataTemplate of Pivot.ItemTemplate and using data binding to display those pages automatically, this would eliminate the need to write the code in xaml.cs that you showed.
Please, share more details about your app (what you're given and what the end result should look like) so we could help you better.

Unable to reselect single item in listview

I have a listview in WPF in an MVVM/PRISM app which may contain 1-to-many elements. When the listview contains only 1 element, and I select it, I cannot subsequently reselect it even though I set the SelectedIndedx value to -1. Worse, if I make the app update the listview with a different single element, I can't select that one either. The only way I can achieve selection of an item when it is the only item in the listview is to make the app display multiple items and select something other than the first. Then, when I make the app display a listview containing a single item, I can select it again - but only once.
In those cases where I cannot select the single item in the listview, the servicing routine never fires.
I tried implementing a XAML suggestion I found here using "Listview.Container.Style" and the IsSelected property, but that did not work.
My listview is fairly straightforward:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList,Mode=TwoWay}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedIndex="{Binding Path=InstanceSelectedIndex}">
</ListView>
The servicing routine is:
private void OnInstanceSelectedIndexChanged()
{
// Handle case where user hits Enter without making a selection:
if (_instanceIndex == -1) return;
// Get the instance record for the row the user clicked on as a
// ResourceInstance class named "InstanceRecord".
InstanceRecord = _instanceList[_instanceIndex];
_instanceNumber = InstanceRecord.Instance;
FormInstName = InstanceRecord.InstName;
FormInstEnabled = InstanceRecord.Enabled;
FormInstState = InstanceRecord.InitialState;
FormInstIPAddress = InstanceRecord.IPAddress;
FormInstPort = InstanceRecord.Port.ToString();
FormInstSelectedURL = InstanceRecord.UrlHandler;
} // End of "OnResourceSelectedIndexChanged" method.
"InstanceList" is an observable collection.
I'd appreciate some suggestions. Thanks in advance for any help.
In a MVVM scenario, I'd use a ViewModel that contains the selected item instead:
class MyViewModel {
private IList<Item> instanceList= new List<Item>();
public IList<Item> List
{
get {return list; }
set {
list = value;
RaisePropertyChanged(() => List);
}
}
private Item selectedItem;
public Item SelectedItem {
get {return selectedItem;}
set {
selectedItem = value;
RaisePropertyChanged(() => SelectedItem);
}
}}
And the XAML:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}}">
Notice that observableCollection is not required unless you have to modify the list items, in the same way the binding should be the default one for the list.
The SelectedItem / SelectedIndex should be TwoWay or Onewaytosource, the latter if you think you don't need to change the selectedItem programmatically
The service routine should be called from the ViewModel
EDIT:
your code of the service routine should be placed there:
set {
selectedItem = value;
// your code
RaisePropertyChanged(() => SelectedItem);
}
Another valid approach is to use Blend on XAML, by invoking a command on changed index and process under the ViewModel.
To do this, first add reference to System.Windows.Interactivity in your project and in XAML add
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity
Then modify ListView with the following:
<ListView Name="lstEditInstance"
Grid.Row="5"
ItemsSource="{Binding Path=InstanceList}"
Width="488"
FontFamily="Arial" FontSize="11"
HorizontalAlignment="Left" VerticalAlignment="Stretch"
Margin="10,96,0,28"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}}">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="SelectionChanged">
<interactivity:InvokeCommandAction Command="{Binding YourCommand}"
CommandParameter="{Binding YourCommandParameter}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>

Accessing ListBox and other children in a LongListSelector

I'm building an app where there's a arbitrarily long list of items where you can click on any of them and edit in place. As you edit any of these items I want to programmatically change focus to the next item in the list.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,-10,0,-12">
<TextBox x:Name ="tb" Text="{Binding TheText}" TextWrapping="Wrap" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
As the user types in the first TextBox and his text exceeds a certain # of characters (e.g. 138) I want to either add another item to the list as the next item and change focus to it, or, if there's already a next item, change focus to it.
I can't figure out how to get access to
1) The root list box
2) The TextBox control within an item given a list box item ID
Here's what I tried. When this runs, the MainLongListSelector.SelectedITem = nextItem causes the next item to be selected, but it does NOT get focus.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
var editBox = sender as TextBox;
var selectedItem = MainLongListSelector.SelectedItem as ItemViewModel;
if (editBox != null && selectedItem != null && editBox.Text.Length > 138) {
// Move data at end to next box
var overflow = editBox.Text.Substring(138, editBox.Text.Length - 138) ;
selectedItem.Tweet = editBox.Text.Substring(0, 138);
var nextItem = App.ViewModel.Items[int.Parse(selectedItem.ID) + 1];
nextItem.Tweet = overflow;
MainLongListSelector.SelectedItem = nextItem;
}
}
I want to be able to access the actual TextBox of that nextItem so I can explicitly set focus to it.
The same question applies if I just use ListBox but the issues are different. In the ListBox case when the DataTemplate contains a TextBox and focus is set, I don't get SelectionChanged events...which is why I'm sticking with LongListSelector.
If I understand you correctly, you want to the TextBox get focus when the item item selected.
If this is what you need, here is a way to bind LisBoxItem.IsSelected To Element.Focus():
http://social.msdn.microsoft.com/Forums/en-US/adeb3e7f-16df-4c7b-b2d2-d7cdedb32ac0/setting-focus-of-a-textbox-inside-a-listbox?forum=wpf

Force selection by index of listbox or take apart same values

I use textbox and button to add items to listbox:
if (tbName.Text != "")
{
listBox.Items.Add(tbName.Text);
//Let user add another new items
tbName.Text = "";
}
And try to select items doing something (as I edit it).
But the trouble is the user can add same value of item, and while I try to select one of the item, it will result in failure. It will auto-select two items by same name, and the operation will be improper.
Can I use some method to force the listbox select items by index( since the index shouldn't be same)? Or how can I take apart the same value of items by programming(it won't select same item but just select what I click, is it means I should intercept the event of MouseDown or SelectChanged by select items refer to the position of mouse click, like height of the line is 18px, so if the position I click is between 18~36px, then the index of select item should be 1, and 0 is 0~18px)?
My listBox is:
<ListBox Height="248" HorizontalAlignment="Left" Margin="141,223,0,0" Name="listBox" VerticalAlignment="Top" Width="378" />
I think that if you set the SelectionMode="Single" property of the Listbox may help you.
use SelectedItem or SelectedInbdex
I use ObservableCollection finally. Although the question I still haven't been solved.
XAML :
<ListBox Height="248" HorizontalAlignment="Left" Margin="141,223,0,0" Name="listBox" VerticalAlignment="Top" Width="378">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Location, Mode=TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And code:
ObservableCollection<locateItem> locatList = new ObservableCollection<locateItem>();
public class locateItem
{
public string Location { get; set; }
public string Id { get; set; }
public string Img { get; set; }
}
Then
listBox.ItemsSource = locatList;
Thanks Sheridan

WPF MVVM Prevent tab change

I am having a WPF Tab control with two tabs called "OFFLINE" & "ONLINE". When "ONLINE" tab click, I need to chek whether application is in online status or not.If (status ! = online)
display error message and prevent displaying "ONLINE" (2nd) tab and go to "OFFLINE" tab.
VIEW.XAML
<TabControl Name="dashboardTabControl" SelectedIndex="{Binding SelectedTabIndex,Mode=TwoWay}">
<TabItem Header="Local Dashboard">
<views:OfflineDashboard DataContext="{Binding OfflineDashboardViewModel}"/>
</TabItem>
<TabItem Header="Online Dashboard">
<views:OnlineDashboard DataContext="{Binding OnlineDashboardViewModel}"/>
</TabItem>
</TabControl>
VIEWMODEL
public int SelectedTabIndex
{
get
{
return this.selectedTabIndex;
}
set
{
if (value == 1 && !applicationData.IsApplicationOnline())
{
this.SelectedTabIndex = 0;
}
else
{
this.selectedTabIndex = value;
}
// TODO : According to the selected tab index , populate ONLINE/OFFLINE
viewmodels
NotifyPropertyChange("SelectedTabIndex");
}
}
Question : Although i checked status and set tab to 0, it doesn't work.
always onclick of 2nd tab it will displayed ONLINE tab.
I would do this the other way around.
Have the ViewModel listen for network changes and expose a bool property Online.
Bind the Enabled property of the Tabpages to this bool.
That way you do not pollute the ViewModel with UI code.
Finally I found a solution for my problem:
XAML
<TabControl Name="dashboardTabControl" Margin="0,5,0,0" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TabItem Header="Local Dashboard" IsSelected="{Binding IsOnline,Converter={StaticResource invertBoolConverter}}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Top">
<views:OfflineDashboard DataContext="{Binding OfflineDashboardViewModel}"/>
</TabItem>
<TabItem Header="Online Dashboard" IsSelected="{Binding IsOnline}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Top">
<views:OnlineDashboard DataContext="{Binding OnlineDashboardViewModel}"/>
</TabItem>
Now I am using IsSelected property of TabItem, instead of SelectedIndex
VIEWMODEL
public bool IsOnline
{
get
{
return isOnline;
}
set
{
// When ONLINE tab click, check whether application is online,
// if not, do not display ONLINE tab
if (value && !applicationData.IsApplicationOnline())
{
isOnline = false;
return;
}
else
{
isOnline = value;
}
LoadTabContent();
NotifyPropertyChange("IsOnline");
}
}
This solved my problem.
You should implement INotifyPropertyChanged in your ViewModel. After changing the SelectedTabIndex notify View that selected index of tab control has been changed via PropertyChanged event of INotifyPropertyChanged.
And in your XAML do
SelectedIndex="{Binding SelectedTabIndex,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}

Categories

Resources