Disable pivot swipe during scrolling on UWP app - c#

I'm developing Universal Window App on Windows 10 platform and my page looks like this example :
Main page contain a pivot control with 4 pivot items (PAGE 1, PAGE 2, ...).
PAGE 1 contain a red stackpanel which contain a listview with horizontal scrolling.
My problem is, when i want to scroll my red listview horitontaly, my pivot swipe to next page.
I want to disable pivot swipe during listview scrolling (But only during listview scrolling).
I tried to get scrollviewer from listview and to listen viewChange from scrollviewer to disable pivot swipe but without success.
Everything work but set IsHitTestVisible to false seems to not work.
Here is my code :
ScrollViewer scrollViewer = ListViewHelper.GetScrollViewer(myListView);
scrollViewer.ViewChanged += scrollview_ViewChanged;
private void scrollview_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var scrollviewer = sender as ScrollViewer;
if (e.IsIntermediate)
{
mainPivot.IsHitTestVisible = false;
} else
{
mainPivot.IsHitTestVisible = true;
}
}
And my ListViewHelper class :
public static class ListViewHelper
{
public static ScrollViewer GetScrollViewer(this DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
}
And my xaml code :
<Pivot x:Name="mainPivot">
<PivotItem x:Name="pivot1">
<!-- Header -->
<PivotItem.Header>
<controls:TabHeader x:Uid="pivot1HeaderTitle"
Label=""
Glyph=""/>
</PivotItem.Header>
<!-- Content -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="110" />
<RowDefinition Height="30" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel x:Name="localeEditionsFavory"
Grid.Row="0">
[...]
<!-- HERE is my listview -->
<ListView x:Name="localeEditionsFavoryList"
Height="80"
ItemsSource="{Binding FavoritesItems}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="Margin" Value="10" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="favoryList">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="55" />
</Grid.RowDefinitions>
<Rectangle x:Name="strokedasharray"
Grid.Column="0"
Grid.Row="0"
Fill="White"
Opacity="0.2"
RadiusX="5"
RadiusY="5"/>
<TextBlock Grid.Column="0"
Grid.Row="0"
Text="{Binding FavoriteItem.EditionKey}"
TextWrapping="WrapWholeWords"
Height="auto"
Foreground="White"
FontSize="14"
Margin="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
TextAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
[...]
<ScrollViewer VerticalScrollMode="Auto"
VerticalScrollBarVisibility="Auto"
Grid.Row="3">
<controls:NewsItemControl x:Name="NewsListControl"
Visibility="{Binding Busy, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
</ScrollViewer>
</Grid>
</PivotItem>
[...]
Does any one has an idea to resolve this problem ?

The undesired behavior you're seeing is a result of scroll chaining. Both the ListView and Pivot contain ScrollViewers in their control templates making this a nested ScrollViewer situation. When you have a ScrollViewer contained within the tree of another and attempt to scroll via touch beyond the extent of the inner ScrollViewer then the outer ScrollViewer engages and begins scrolling.
With chaining disabled you’d only be able to scroll the outer ScrollViewer by beginning the manipulation on a hit-testable area of the outer ScrollViewer. Chaining is enabled by default on the ScrollViewer so when you attempt to scroll past the end of the list the chaining kicks in and is recognized by the Pivot as an attempt to “scroll” to the next pivot item.
Disable chaining on the ListView's ScrollViewer (and remove the ListViewHelper stuff in code behind) by setting the ScrollViewer.IsHorizontalScrollChainingEnabled="False" attached property:
<!-- HERE is my listview -->
<ListView x:Name="localeEditionsFavoryList"
Height="80"
ItemsSource="{Binding FavoritesItems}"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled">

If you need to make the ListView horizontal scrolling, you can achieve this just in XAML code by modifying the style of the ListView like this:
<Style x:Key="ListViewStyle" TargetType="ListView">
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.HorizontalScrollMode" Value="Enabled" />
<Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="True" />
<Setter Property="ScrollViewer.VerticalScrollMode" Value="Disabled" />
<Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Now if you disable the scrolling of Pivot when you scroll the ListView of "PAGE 1", you can edit your code like this:
public void Page_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindChildOfType<ScrollViewer>(mainPivot);
scrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
}
private void PivotSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (mainPivot.SelectedIndex == 0)
{
var scrollViewer = FindChildOfType<ScrollViewer>(mainPivot);
scrollViewer.HorizontalScrollMode = ScrollMode.Disabled;
}
}
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}

Related

UWP GridView ItemTemplateSelector in SemanticZoom not applying

I have a GridView as my zoomed out view in a SemanticZoom control. This GridView uses a custom DataTemplateSelector as the ItemTemplateSelector. The DataTemplateSelector returns a DataTemplate with different Foreground color, depending upon whether or not the Group has any items in it.
However, even though the DataTemplateSelector seems to work and returns the correct template, only one template is ever used by the GridView and the text is all the same color.
Here is the XAML of the GroupedListView:
<UserControl
x:Class="GroupList.GroupList.GroupedListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GroupList.GroupList"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:GroupList.Model"
xmlns:wuxdata="using:Windows.UI.Xaml.Data"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Use a collection view source for content that presents itself as a list of items that can be grouped or sorted. Otherwise, you can use x:Bind
directly on the ListView's item source to for further optimization. Please see the AppUIBasics sample for an example of how to do this. -->
<CollectionViewSource x:Name="ContactsCVS" IsSourceGrouped="True" />
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyle" BasedOn="{StaticResource ProximaNovaSemiBold}">
<Setter Property="FontSize" Value="54" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontWeight" Value="ExtraBold"/>
<Setter Property="FontStretch" Value="Expanded" />
</Style>
<Style TargetType="TextBlock" x:Key="TileHeaderTextStyleGray" BasedOn="{StaticResource TileHeaderTextStyle}">
<Setter Property="Foreground" Value="Khaki" />
</Style>
<!-- When using x:Bind, you need to set x:DataType -->
<DataTemplate x:Name="ContactListViewTemplate" x:DataType="data:Contact">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse x:Name="Ellipse"
Grid.RowSpan="2"
Width ="32"
Height="32"
Margin="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Fill="LightGray"/>
<TextBlock Grid.Column="1"
Text="{x:Bind Name}"
x:Phase="1"
Style="{ThemeResource BaseTextBlockStyle}"
Margin="12,6,0,0"/>
<TextBlock Grid.Column="1"
Grid.Row="1"
Text="{x:Bind Position}"
x:Phase="2"
Style="{ThemeResource BodyTextBlockStyle}"
Margin="12,0,0,6"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="GrayZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyleGray}" />
</DataTemplate>
<DataTemplate x:Key="ZoomedOutTemplate" x:DataType="wuxdata:ICollectionViewGroup">
<TextBlock Text="{x:Bind Group.(data:GroupInfoList.Key)}" Margin="0,0,0,5" Style="{StaticResource TileHeaderTextStyle}" />
</DataTemplate>
<local:GroupEmptyOrFullSelector x:Key="GroupEmptyOrFullSelector" Empty="{StaticResource GrayZoomedOutTemplate}" Full="{StaticResource ZoomedOutTemplate}" />
</UserControl.Resources>
<!--#region Navigation Panel -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Margin="15,0,0,0" Text="Paula's SemanticZoom Sandbox" Grid.Row="0"
VerticalAlignment="Center"
Style="{ThemeResource TitleTextBlockStyle}" />
<Button x:Name="ZoomInOutBtn" Content="ABC↕" Click="ZoomInOutBtn_Click" Width="60" HorizontalAlignment="Center" BorderThickness="0" />
</StackPanel>
<!--#endregion-->
<SemanticZoom x:Name="ZoomControl" Grid.Row="1">
<SemanticZoom.ZoomedInView>
<GridView ItemsSource="{x:Bind ContactsCVS.View}"
ItemTemplate="{StaticResource ContactListViewTemplate}"
SelectionMode="Single"
ShowsScrollingPlaceholders="True">
<GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="data:GroupInfoList">
<TextBlock Text="{x:Bind Key}"
Style="{ThemeResource TitleTextBlockStyle}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<GridView ItemTemplateSelector="{StaticResource GroupEmptyOrFullSelector}" ScrollViewer.VerticalScrollBarVisibility="Disabled" Margin="0, 200" Width="475" ItemsSource="{x:Bind ContactsCVS.View.CollectionGroups}" SelectionMode="None" >
</GridView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
</Grid>
</UserControl>
And here is the DataTemplateSelector:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.Foundation.Collections;
using GroupList.Model;
namespace GroupList.GroupList
{
/// <summary>
/// This determines whether or not the Group passed during binding is empty or not and allows selection
/// of the proper DataTemplate based on this value.
/// </summary>
class GroupEmptyOrFullSelector : DataTemplateSelector
{
private DataTemplate _full;
private DataTemplate _empty;
public DataTemplate Full
{
set { _full = value; }
get { return _full; }
}
public DataTemplate Empty
{
set { _empty = value; }
get { return _empty; }
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
}
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
}
}
So, here was the problem:
In the DataTemplateSelector listing, even if a passed "object item" was not a group, it fell through to the template selection logic. Sometimes, the "object" passed to your function is not a Group object, but rather a generic DependencyObject. In such a case, your template selection logic needs to return a default template, returning one of the specific templates only if the "object item" is a Group object. Thus, the new function looks like this:
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemType = item.GetType();
var isGroup = itemType.Name == "GroupInfoList";
bool isEmpty = false;
GroupInfoList groupItem;
if (isGroup)
{
groupItem = item as GroupInfoList;
isEmpty = groupItem.Count == 0;
// Disable empty items
var selectorItem = container as SelectorItem;
if (selectorItem != null)
{
selectorItem.IsEnabled = !isEmpty;
}
if (isEmpty)
{
return Empty;
}
else
{
return Full;
}
}
return Full;
}

Creating an Expand All and Collapse All Buttons with Expander in WPF

I am working in Visual Studio 2013 in WPF (C#) and I have the following code to create an expander from my xml. It is currently working perfectly right now but I want to include an expand all and collapse all buttons. I have looked all over and can't seem to find a solution.
Here is where the expander is created. I know I just have to iterate through a list of items and change the Property="IsExpanded" to Value="True" to create an expand all.
<DataTemplate x:Key="dtListTemplate" >
<StackPanel>
<Expander LostFocus="CollapseExpander" ExpandDirection="Down" Width="Auto">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="False" />
<Setter Property="Header" Value="{Binding XPath=#Name}" />
<Setter Property="FontWeight" Value="Bold"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded,RelativeSource={RelativeSource Self}}" Value="True">
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<ListBox Name="itemsList"
ItemsSource="{Binding XPath=UpgradeAction}"
ItemTemplate="{StaticResource dtListItemTemplate}"
SelectionChanged="listItems_SelectionChanged"
Style="{StaticResource styleListBoxUpgradeAction}"
ItemContainerStyle="{StaticResource styleListBoxItemUpgradeAction}">
</ListBox>
</Expander>
</StackPanel>
</DataTemplate>
Here's the code that calls the DataTemplate which has information from an Xml.
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Border Grid.Column="0" Grid.Row="0" Width="790" Height="40" Padding="5" Background="#4E87D4">
<Label VerticalAlignment="Center" FontSize="16" FontWeight="Bold" Foreground="White">Test</Label>
</Border>
<Button Name="ExpandBttn" Width="100" Height="40" FontSize="16" FontWeight="Bold" Content="Expand All" DataContext="{Binding}" Click="Expand_Button_Click"/>
<Button Name="ColapseBttn" Width="100" Height="40" FontSize="16" FontWeight="Bold" Content="Colapse All" DataContext="{Binding}" Click="Collapse_Button_Click"/>
</StackPanel>
<ListView Name="listItems" Grid.Column="0" Grid.Row="1" Background="Wheat"
ItemsSource="{Binding Source={StaticResource xmldpUpgradeActions}, XPath=ActionGroup}"
ItemTemplate="{StaticResource dtListTemplateRichards}"
SelectionChanged="listItems_SelectionChanged">
</ListView>
</StackPanel>
Here's what I tried in the .cs file for the expand all portion.
private void Expand_Button_Click(object sender, RoutedEventArgs e)
{
foreach(var item in listItems.Items)
{
var listBoxItem = listItems.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
var itemExpander = (Expander)GetExpander(listBoxItem);
if (itemExpander != null)
itemExpander.IsExpanded = true;
}
}
private static DependencyObject GetExpander(DependencyObject container)
{
if (container is Expander) return container;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
{
var child = VisualTreeHelper.GetChild(container, i);
var result = GetExpander(child);
if (result != null)
{
return result;
}
}
return null;
}
Any help is greatly appreciated!
Is xmldpUpgradeActions a CollectionViewSource?
Whatever class is in the collection, it should implement INotifyPropertyChanged.
Give it an IsExpanded property that raises PropertyChanged in its setter when its value changes, and bind that to Expander.IsExpanded in the template:
<Expander
IsExpanded="{Binding IsExpanded}"
LostFocus="CollapseExpander"
ExpandDirection="Down"
Width="Auto">
Write a command that loops through all the items in the collection and sets item.IsExpanded = false; on each one.

Hide ListView when no results in ItemsSource

I'm using Visual Studio 2015 and MVVM Light Toolkit to build a WPF app. When the user clicks an employee in a DataGrid, we show the record's details to allow editing. This details area consists of two tabs: Demographics and Tests. The Tests tab displays a ListView of the tests for this person.
Here's the structure:
MainWindow.xaml:
<DataTemplate x:Key="EmployeeSearchTemplate">
<view:EmployeeSearchView />
</DataTemplate>
<ContentControl ContentTemplate="{StaticResource EmployeeSearchTemplate}" />
EmployeeSearchView.xaml:
<UserControl.DataContext>
<viewModel:EmployeeSearchViewModel />
</UserControl.DataContext>
<ContentControl Content="{Binding SelectedEmployee}"
ContentTemplate="{StaticResource EmployeeViewTemplate}" .../>
When the user selects the Tests tab, we search the db and return the tests for this employee, if any.
EmployeeView.xaml:
<DataTemplate x:Key="TestsViewTemplate">
<views:TestsView />
</DataTemplate>
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem>
<!-- Demographic details of record here -->
</TabItem>
<TabItem>
<!-- Employee test info here. When user selects this tab, search db
and return tests for this employee, if any -->
<ContentControl Content="{Binding TestsVm}"
ContentTemplate="{StaticResource TestsViewTemplate}" />
</TabItem>
</TabControl>
Here are the constructor and some properties for EmployeeViewModel.cs:
private TestsViewModel _testsVm;
private int _selectedTabIndex;
public EmployeeViewModel ()
{
// Other initialization code...
_selectedTabIndex = 0;
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(SelectedTabIndex))
{
// If tab 1 selected, the person is on the Tests tab
// Perform search and populate the TestsVM object's Tests
// by executing the RelayCommand on it
if (SelectedTabIndex.Equals(1))
{
TestsVm = new TestsViewModel
{
SelectedEmployeeId = EmployeeId
};
TestsVm.SearchTestsRelayCommand.Execute(null);
}
}
};
}
public TestsViewModel TestsVm
{
get { return _testsVm; }
set
{
if (Equals(value, _testsVm)) return;
_testsVm = value;
RaisePropertyChanged();
}
}
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value == _selectedTabIndex) return;
_selectedTabIndex = value;
RaisePropertyChanged();
}
}
Here's the ListView in TestsView.xaml:
<ListView ItemsSource="{Binding Tests}"
Visibility="{Binding HasTests,
Converter={helpers:BooleanToVisibilityConverter WhenTrue=Visible,
WhenFalse=Hidden}}">
<ListView.View>
<GridView>
<!-- GridView columns here -->
</GridView>
</ListView.View>
</ListView>
Here's code from TestsViewModel.cs:
private ObservableCollection<TestViewModel> _tests;
private int _selectedEmployeeId;
private bool _hasTests;
public TestsViewModel()
{
SearchTestsRelayCommand = new RelayCommand(CallSearchTestsAsync);
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(Tests))
{
HasTests = !Tests.Count.Equals(0);
}
};
}
public RelayCommand SearchTestsRelayCommand { get; private set; }
private async void CallSearchTestsAsync()
{
await SearchTestsAsync(SelectedEmployeeId);
}
private async Task SearchTestsAsync(int employeeId)
{
ITestDataService dataService = new TestDataService();
try
{
Tests = await dataService.SearchTestsAsync(employeeId);
}
finally
{
HasTests = !Tests.Count.Equals(0);
}
}
public ObservableCollection<TestViewModel> Tests
{
get { return _tests; }
set
{
if (Equals(value, _tests)) return;
_tests = value;
RaisePropertyChanged();
}
}
public bool HasTests
{
get { return _hasTests; }
set
{
if (value == _hasTests) return;
_hasTests = value;
RaisePropertyChanged();
}
}
public int SelectedEmployeeId
{
get { return _selectedEmployeeId; }
set
{
if (value == _selectedEmployeeId) return;
_selectedEmployeeId = value;
RaisePropertyChanged();
}
}
The HasTests property is not changing and thus not hiding the ListView when it's empty. Note that I also tried the following for the ListView visibility, pointing to its own HasItems to no avail:
Visibility="{Binding HasItems,
RelativeSource={RelativeSource Self},
Converter={helpers:BooleanToVisibilityConverter WhenTrue=Visible,
WhenFalse=Hidden}}"
I've used the same BooleanToVisibilityConverter successfully elsewhere, so it's something with my code. I'm open to your suggestions. Thank you.
Update: Here's the XAML for TestView.xaml:
<UserControl x:Class="DrugComp.Views.TestsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:DrugComp.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:viewModel="clr-namespace:DrugComp.ViewModel"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources />
<Grid Width="Auto"
Height="700"
Margin="5,7,5,5"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="32" />
<RowDefinition Height="32" />
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Style="{StaticResource Instruction}"
Text="{Binding Instructions}" />
<ListView Grid.Row="1"
Grid.ColumnSpan="2"
Width="Auto"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Top"
AlternationCount="2"
ItemContainerStyle="{DynamicResource CustomListViewItemStyle}"
ItemsSource="{Binding Tests}"
SelectedItem="{Binding SelectedTest}">
<ListView.Style>
<Style TargetType="{x:Type ListView}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<!-- If you want to save the place in the layout, use
Hidden instead of Collapsed -->
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
<ListView.View>
<GridView>
<GridViewColumn Width="50"
DisplayMemberBinding="{Binding TestId}"
Header="Test ID" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding EmployeeId}"
Header="Employee ID" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding OrderedDate,
StringFormat='MM/dd/yyyy'}"
Header="Ordered Date" />
<GridViewColumn Width="119"
DisplayMemberBinding="{Binding ValidReasonForTest.Description}"
Header="Reason" />
<GridViewColumn Width="129"
DisplayMemberBinding="{Binding OrderedByWhom}"
Header="Ordered By" />
<GridViewColumn Width="90"
DisplayMemberBinding="{Binding ScheduledDate,
StringFormat='MM/dd/yyyy'}"
Header="Scheduled Date" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
As Joe says, you're not getting the notifications. And if you need HasTests for some reason other than hiding this ListView, his answer will help. But that's not the way to do this in a view in XAML.
Update:
A cleaner, simpler way than the answer below.
<!-- In the view's Resources -->
<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
<!-- ... -->
<ListView
Visibility="{Binding HasItems,
RelativeSource={RelativeSource Self},
Converter=BooleanToVisibility}" />
The (second) cleanest, simplest, easiest way is with a trigger in a style, like this:
<ListView>
<ListView.View>
<GridView>
<!-- GridView columns here -->
</GridView>
</ListView.View>
<ListView.Style>
<Style
TargetType="{x:Type ListView}"
BasedOn="{StaticResource {x:Type ListView}}">
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<!-- If you want to save the place in the layout, use
Hidden instead of Collapsed -->
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Style>
</ListView>
Just note that you can't set the Visibility attribute in the XAML like so, because that's a "local" value which will supersede anything the Style does:
<ListView Visibility="Visible" ...>
That's desirable behavior when you want to override styling for a specific instance of a control, but it bites you a lot when you write triggers.
In this specific case I can't imagine any reason you'd do that, but it's a pervasive "gotcha" with styles and triggers in XAML. If you want to set a specific initial value for a property that'll be driven by a trigger, you can do that in a non-triggered Setter in the Style:
<Style
TargetType="{x:Type ListView}"
BasedOn="{StaticResource {x:Type ListView}}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<!-- If you want to save the place in the layout, use
Hidden instead of Collapsed -->
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
Then it's all one style thing or another, and the trigger will work.
Any descendant of ItemsControl will support the HasItems property: ListBox, ComboBox, MenuItem, you name it. Pretty much any native WPF control that's designed to present a dynamic collection of items (third party control vendors like DevExpress will often ignore this and use their own, often ill-considered, class hierarchy). It's idea for this sort of thing because it's always there, it's very easy to use, and it doesn't matter where the items come from. Whatever you do to put items in that thing, it will hide itself when there aren't any.
Your code to update HasTests:
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(Tests))
{
HasTests = !Tests.Count.Equals(0);
}
};
Will only fire when the whole property Tests is changed (i.e. assigned to a new ObservableCollection). Presumably, you're not doing this, instead using Clear, Add or Remove to change the content of Tests.
As a result, your HasTests never get's updated. Try also updating on the Tests.CollectionChange event to catch adding/removing.
Edit: Something like this
this.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(Tests))
{
HasTests = !Tests.Count.Equals(0);
//also update when collection changes:
Tests.CollectionChanged += (o2, e2) =>
{
HasTests = !Tests.Count.Equals(0);
};
}
};

GridSplitter disables my RowDefinition style

I have a Grid that contains three rows, and the third row visibility is bound to a boolean:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
<RowDefinition Height="*" />
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="{x:Type RowDefinition}">
<Setter Property="Height"
Value="0.35*" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=plotter2, Path=Visibility}" Value="Collapsed">
<Setter Property="Height" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
I also have a GridSplitter:
<GridSplitter
ResizeDirection="Rows"
ResizeBehavior="BasedOnAlignment"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
Width="Auto"
Height="6"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Margin="0 20 0 0"
Background="Transparent"/>
The purpose of this is that when I click a Checkbox the last row appears and occupy 35% of the second, but I can resize it with the GridSplitter. When the Checkbox is unchecked that row's visibility is Collapsed.
The problem is that if I use the GridSplitter the new height seems to overwrite the initial style definition and when I uncheck my Checkbox the last row remains visible.
I don't have any idea to some this, could anyone give me a hint? Thanks
If you use DependencyPropertyHelper.GetValueSource(Rowdefinition.HeightProperty) you can see that initially the property value is set by the style, but after the GridSplitter changes the height of the row the height property has a "local" value. Local values take precedence over all other sources for a dependency value, so when the data trigger attempts to set the height property to 0 nothing happens.
On top of this, the GridSplitter sets the height of the row above it and as soon as relative (star) values are involved the heights set look somewhat unfriendly.
Here is the way I found to work round this problem:
when hiding the row clear the value of the height property before attempting to set the height to 0
AND
when making the row visible clear the value of the height property of the upper row to reset the height to its initial value.
This second step caused me the biggest headache. The "unfriendly" height values set by the GridSplitter can be 181.7634545* for example. If you want to show the bottom row again and you give it a height of 0.35* it looks like it's not being made visible as it's only a couple of pixels high!
Unfortunately I haven't found a way to do this all in XAML. The solution relies on programmatically resetting the two affected rows heights at the appropriate moment.
In case the text isn't quite clear here's the code I used to test this:
MainWindow.cs.xaml:
<Window x:Class="GridRowHidingSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="120" />
<RowDefinition Name="TopRow" />
<RowDefinition Name="BottomRow">
<RowDefinition.Style>
<Style TargetType="{x:Type RowDefinition}">
<Setter Property="Height" Value="0.35*" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsRowVisible, Mode=OneWay}" Value="False">
<Setter Property="Height" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
</Grid.RowDefinitions>
<Border Background="Yellow">
<CheckBox Content="Show bottom row" HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding Path=IsRowVisible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10" />
</Border>
<Border Background="Red" Grid.Row="1">
</Border>
<Border Grid.Row="2" Background="Green">
</Border>
<GridSplitter Grid.Row="2" Height="10" HorizontalAlignment="Stretch" VerticalAlignment="Top" Background="DarkGray" Margin="0,20,0,0" />
</Grid>
MainWindow.cs:
using System.Windows;
namespace GridRowHidingSample
{
public partial class MainWindow : Window
{
private MainWindowViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainWindowViewModel(TopRow, BottomRow);
DataContext = _viewModel;
}
}
}
And finally the view model. The important bit is the method ResetHeight() and the Property IsRowVisible where the method is called.
MainWindowViewModel.cs:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace GridRowHidingSample
{
public class MainWindowViewModel : INotifyPropertyChanged
{
private RowDefinition _topRow;
private RowDefinition _bottomRow;
private bool _isRowVisible = false;
public MainWindowViewModel(RowDefinition topRow, RowDefinition bottomRow)
{
_topRow = topRow;
_bottomRow = bottomRow;
}
private void ResetHeight(RowDefinition rowDefinition)
{
if (rowDefinition != null)
{
if (DependencyPropertyHelper.GetValueSource(rowDefinition, RowDefinition.HeightProperty).BaseValueSource == BaseValueSource.Local)
rowDefinition.ClearValue(RowDefinition.HeightProperty);
}
}
public bool IsRowVisible
{
get { return _isRowVisible; }
set
{
if (_isRowVisible != value)
{
_isRowVisible = value;
NotifyPropertyChanged("IsRowVisible");
if (_isRowVisible)
ResetHeight(_topRow);
else
ResetHeight(_bottomRow);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Hope this helps you, if you haven't already found another solution.

How to dynamically change columns with rows so WPF UserControl appearance is different for each screen

I have a UserControl that is made of two grids.
There are two columns in the grid side by side and this grid has the horizontal layout.
Column 0 has one data and Column 1 has another data.
I need to have this control to be able to change the layout from horizontal to vertical depending on the screen this control should be displayed in, so instead of having Columns 0 and 1 displaying the data, the Row 0 and Row 1 should now display the data.
What is the best way to accomplish this?
Thank you
Try to use StackPanel and change property Orientation from Horizontal to Vertical.
You can use ControlTemplates and DataTriggers to choose this, if you want more control than a StackPanel can give you. Here's a quick-n-dirty example.
Note that you can do this without using a UserControl. I'm just staying within the confines of your description.
User Control
<UserControl>
<UserControl.Resources>
<ControlTemplate x:Key="usingColumns">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding DataOneItems}" />
<ListBox Grid.Column="1" ItemsSource="{Binding DataTwoItems}" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="usingRows">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding DataOneItems}" />
<ListBox Grid.Row="1" ItemsSource="{Binding DataTwoItems}" />
</Grid>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style>
<Setter Property="UserControl.Template" Value="{StaticResource usingColumns}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowVertically}" Value="true">
<Setter Property="UserControl.Template" Value="{StaticResource usingRows}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
</UserControl>
Class binding to User Control:
public class UserControlData
{
public ReadOnlyObservableCollection<DataTypeOne> DataOneItems
{
get
{
ObservableCollection<DataTypeOne> dataOneItems = new ObservableCollection<DataTypeOne>();
for (int i = 1; i <= 3; i++)
dataOneItems.Add(new DataTypeOne(i));
return new ReadOnlyObservableCollection<DataTypeOne>(dataOneItems);
}
}
public ReadOnlyObservableCollection<DataTypeTwo> DataTwoItems
{
get
{
ObservableCollection<DataTypeTwo> dataTwoItems = new ObservableCollection<DataTypeTwo>();
for (int i = 1; i <= 3; i++)
dataTwoItems.Add(new DataTypeTwo(i));
return new ReadOnlyObservableCollection<DataTypeTwo>(dataTwoItems);
}
}
public bool ShowVertically
{
get;
set;
}
}
Dummy Data Type (DataTypeOne and DataTypeTwo are identical except in class name):
public class DataTypeOne
{
private readonly int mId = 0;
public DataTypeOne(int id)
{
mId = id;
}
public int ID
{
get { return mId; }
}
public override string ToString()
{
return String.Format("I am a DataTypeOne with ID {0}", mId.ToString("N"));
}
}
The key is the ControlTemplates (one for horizontal, one for vertical) and the DataTrigger on the Style.

Categories