Binding the ZIndex of items in an ItemsControl - xaml

Using Caliburn.Micro for a WinRT application, I would like to control the ZIndex of items displays in an ItemsControl.
When a user taps on an item, it should become the topmost element.
<ItemsControl Background="White" Height="auto" Width="auto" x:Name="Parts"
HorizontalAlignment="Left"
VerticalAlignment="Top"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The viewmodel bound to the view above contains a property Parts:
private BindableCollection<IPartViewModel> _parts = new BindableCollection<IPartViewModel>();
public BindableCollection<IPartViewModel> Parts
{
get { return _parts; }
set { _parts = value; NotifyOfPropertyChange(() => Parts); }
}
IPartViewModel has different implementations, each with their own View (= custom user controls).
Every implementation of IPartViewModel has a ZIndex property, ready to be bound.
All other bindings (labels, the tapped event, ...) work perfectly, but I cannot figure out what the binding should look like to control the ZIndex.
Many other questions on SO deal with this issue, but none for WinRT.

I ended up achieving this with a custom ItemsControl and overriding the method GetContainerForItemOverride where I return a ContentPresenter with a binding added to the ZIndex property
protected override DependencyObject GetContainerForItemOverride()
{
var cp = new ContentPresenter();
cp.SetBinding(Canvas.ZIndexProperty, new Binding { Path = new PropertyPath("ZIndex") });
return cp;
}

Related

Retrieve Value from a User control using Dependancy property (UWP)

I have a custom user control like this
<Grid>
<ListView x:Name="LView" SelectedIndex="{Binding SelectedIndex}" ItemsSource="{x:Bind
ItemsSource, Mode=OneWay}" Width="{x:Bind Width}" Height="{x:Bind Height}" VerticalAlignment="Stretch" SelectionMode="Multiple" />
</Grid>
Now in Codebehind, I am trying to get its SelectedIndex using a dependency property
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(CustomControl), new PropertyMetadata(0));
And In my main page, I am accessing that dependency property like this
<local:CustomControl Grid.Column="0" Grid.Row="0" Width="400" Loaded="EditTextControl_Loaded"
x:Name="MultiCombo" ></local:CustomControl>
Codebehind
var selIndex = MultiCombo.SelectedIndex;
but neither an event is getting fired on the selectedIndexChange (in the main page) nor I am getting any value on my main page. How can I make this happen?
Note: I have uploaded complete code here
In your CustomControl page, the mode you bind the SelectedIndex property of ListView with SelectedIndex dependency property is OneWay, when you select other items in ListView, the SelectedIndex dependency property won't change, so the value of MultiCombo.SelectedIndex in main page won't change. In this case, you need to set the mode as TwoWay.
CustomControl.xaml:
<ListView x:Name="LView" SelectedIndex="{x:Bind SelectedIndex,Mode=TwoWay}" ItemsSource="{x:Bind ItemsSource, Mode=OneWay}" Width="{x:Bind Width}" Height="{x:Bind Height}" VerticalAlignment="Stretch" SelectionMode="Multiple" />
In your main page, you subscribe the DataContextChanged event to get the SelectedIndex dependency property, but this event only occurs when the DataContext of current page changes. If you want to trigger method in your main page when the selected index of ListView changes, you can define a dependency property in your main page to bind with the SelectedIndex dependency property of CustomControl and add a static callback method that is automatically invoked whenever a property value change is detected. For example:
MainPage.cs:
public int MPSelectedIndex
{
get { return (int)GetValue(MPSelectedIndexProperty); }
set { SetValue(MPSelectedIndexProperty, value); }
}
public static readonly DependencyProperty MPSelectedIndexProperty =
DependencyProperty.Register("MPSelectedIndex", typeof(int), typeof(MainPage), new PropertyMetadata(0, new PropertyChangedCallback(OnDataChanged)));
private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainPage currentPage = d as MainPage;
int count = currentPage.MultiCombo.SelectedIndex;
}
MainPage.xaml:
<local:CustomControl Grid.Column="0" Grid.Row="0" Width="400" Loaded="EditTextControl_Loaded" x:Name="MultiCombo" SelectedIndex="{x:Bind MPSelectedIndex,Mode=TwoWay}" >
</local:CustomControl>
Note:
Since you set the SelectionMode of ListView is Multiple, when you select the first item, the SelectedIndex is 0 and then you also select the second item, the SelectedIndex is still 0. Only when you unselect the first item, the SelectedIndex will change and the method will be triggered.
I think the issue is that you are not binding the SelectedIndex properly.
Instead of binding to self/ ListView's SelectedIndex, you need to bind it to the CustomControl's SelectedIndex DependencyProperty.
<ListView ... SelectedIndex="{Binding
Path=SelectedIndex,
Mode=TwoWay,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type UserControl}}}" .../>
You might need to change the type to your CustomControl's type as necessary (if it is not UserControl).

How to make sure a Popup control match its parent Page when the parent is resized? UWP

I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}

PrepareContainerForItemOverride works different in Desktop than in Mobile UWP

I wanted to change the color of an item of ListView according the data value.
It would be easy doing:
<ListView.ItemContainerStyle>
<Style TargetType = "ListViewItem" >
< Setter Property="Background" Value="{Binding EventType, Converter={StaticResource EventTypeToBackColorConverter}}" />
</ListView.ItemContainerStyle>
But the thing is that UWP does not support binding in Setter Properties.
My second attempt was overriding PrepareContainerForItemOverride of the ListView:
public class EventListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
var ev = item as EventType;
if(ev.Warning)
listViewItem.Background = new SolidColorBrush(Color.Red);
}
}
The above code works fine running in a PC with Windows 10 and UWP. It colors in red some items according the underlying data. When I run the same app in Windows Mobile, at beginning it works fine, but when I scroll up and then I scroll down, returning to the original view that was ok at beginning, now other items are also in red color.
What I am missing?
I am not sure the reason, but the following code works for me:
public class EventListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
var ev = item as EventType;
if(ev.Warning)
listViewItem.Background = new SolidColorBrush(Color.Red);
else
listViewItem.Background = null;
}
}
I have added listViewItem.Background = null
This is because when there are a large number of Items, by default ListView has implement the function of data virtualization. It's not a good idea to disable this function since it can achieve a better performance.
But for your scenario, there is a much easier method to solve your problem. Since you're trying to modify the style of ListViewItem in the code behind, and we can't modify the existed one, we can set a new style of ListViewItem to ListView for example like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
var dynamicStyle = new Style();
dynamicStyle.TargetType = typeof(ListViewItem);
dynamicStyle.Setters.Add(new Setter(BackgroundProperty, Colors.Red));
listView.ItemContainerStyle = dynamicStyle;
}
Only one problem is, if you are setting the Background property to all the ListViewItem, it makes no difference than binding data to the Background property of ListView or setting the Background to ListView like this:
listView.Background = new SolidColorBrush(Colors.Red);
So I just assume that you want to modify the root control in the DataTemplate for example like the Grid in the following xaml:
<ListView x:Name="listView" ItemsSource="{x:Bind collection}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" x:Name="myListItemStyle">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Testtext}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then in this scenario, you can use data binding probably like this:
<DataTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{Binding EventType, Converter={StaticResource EventTypeToBackColorConverter}}">
<TextBlock Text="{Binding Testtext}" />
</Grid>
</DataTemplate>
Any way, if you insist to change some property of all ListViewItem in the ListView, you can use the first method I provided.

Windows Store App binding of CheckBox does not work (occassionally)

I have the following piece of XAML:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Background="#286C9A" Width="336" Height="22">
<TextBlock Text="{Binding Checked}" Foreground="Black" HorizontalAlignment="Left"/>
<CheckBox IsChecked="{Binding Checked}" HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
It's a template in a ListView. Checked is a bool property, and the problem is that the initial value of the property is transferred correctly to the view (both TextBlock and CheckBox). But following changes to the property is only reflected on the TextBlock, the Checkbox does not react.
Can someone tell me what happens?
EDIT:
The relevant part (I believe) of the ViewModel is this :
public class MenuGroup : ObservableCollection<MenuItem>
{
bool #checked;
public bool Checked
{
get { return #checked; }
set
{
if (#checked == value) return;
#checked = value;
OnPropertyChanged(new PropertyChangedEventArgs("Checked"));
}
}
}
EDIT: It is apparent that the binding stops working the first time I have clicked on the checkbox and thus manually changed it's state. And it works all the way if it's TwoWay binding. But why that is, I don't know.

Binding ListPicker.SelectedIndex problem

I'm trying to do a two way binding of the SelectedIndex attribute of a ListPicker in a Windows Phone 7 UserControl.
It raises the following exception when I set the DataContext:
SelectedIndex must always be set to a valid value.
This is the XAML code
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<toolkit:ListPicker
Grid.Row="0"
x:Name="List1"
SelectionChanged="Picker_SelectionChanged"
SelectedIndex="{Binding PickerSelectedIndex, Mode=TwoWay}"
ItemTemplate="{StaticResource PickerTemplate}"
ItemsSource="{Binding MyList}"/>
</Grid>
And the code behind in DataContext
private ObservableCollection<MyClass> myList = null;
public ObservableCollection<MyClass> MyList
{
get { return this.myList; }
set
{
if (value != this.myList)
{
this.myList= value;
NotifyPropertyChanged("MyList");
this.PickerSelectedIndex = 0;
}
}
}
private int pickerSelectedIndex = 0;
public int PickerSelectedIndex
{
get
{
return this.pickerSelectedIndex;
}
set
{
this.pickerSelectedIndex= value;
}
}
Putting a breakpoint in PickerSelectedIndex.get I can see that it is returned correctly (0).
I am sure that the problem is SelectedIndex="{Binding PickerSelectedIndex, Mode=TwoWay}" because deleting this line solves the problem, and I can see the ListPicker correctly loaded with the data from MyList.
I can't see where is the problem...
Moving SelectedIndex after ItemsSource solved the problem.
This is the working snippet
<toolkit:ListPicker
Grid.Row="0"
x:Name="List1"
SelectionChanged="Picker_SelectionChanged"
ItemTemplate="{StaticResource PickerTemplate}"
ItemsSource="{Binding MyList}"
SelectedIndex="{Binding PickerSelectedIndex, Mode=TwoWay}"/>
Anyone have an explanation for this?
My guess would be that the binding is being applied with a default value of zero when created and before the items exist. It's therefore trying to select the first item (with a zero index) before that item is created.
Try making sure that the ViewModel property of PickerSelectedIndex defaults to -1.
You may also want to delay setting the binding until the items are created.
Matt Lacey is right; binding happens before the data items get populated & hence the error. If you have an event handler for SelectionChanged, you will notice that a breakpoint in it will be hit as the page/listpicker loads. Here is one way to get around this initialization issue:
private void SomeListPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Make sure we don't handle the event during initiation.
if (e.RemovedItems != null && e.RemovedItems.Count > 0)
{
if (this.SomeListPicker.SelectedItem != null)
{
// Do actual stuff.
}
}
}
I had the same issue and the ordering of the properties in XAML didn't help. In my case, I was binding ItemsSource to a property on a StaticResource, but I was binding SelectedIndex with a property of my page's ViewModel. The moment I changed the binding of ItemsSource to bind to a property on the ViewModel itself (i.e. duplicated a property of the StaticResource), my issue went away and I was able to perform 2-way binding on SelectedIndex as well.
I've found the same problem with my app.
But I noticed that it happens when I delete all the elements of the list bounded to the ListPicker in the ViewModel.
So it isn't necessary to Bind SelectedIndex with another property since the problem depends only on the list bounded.
Here is my code which work fine for me:
<toolkit:ListPicker x:Name="ListaCorsi"
SelectionChanged="ListaCorsi_SelectionChanged"
ItemsSource="{Binding ListaCorsiUser}"
SelectionMode="Single"
ItemTemplate="{StaticResource CorsiDataTemplate}"
ItemsPanel="{StaticResource ItemsPanelTemplateListaCorsi}"/>
The list in view model:
private ObservableCollection<Corsi> _listaCorsiUser;
public ObservableCollection<Corsi> ListaCorsiUser
{
get { return _listaCorsiUser; }
set
{
_listaCorsiUser = value;
OnPropertyChanged("ListaCorsiUser");
}
}
The handler for SelectionChanged:
void ListaCorsi_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListaCorsi.SelectedItem != null)
{
---
this.CorsoSelected = ListaCorsi.SelectedItem as Corsi;
}
}
Where Corsi is the class type of the list.
Here ListPicker template:
<DataTemplate x:Key="CorsiDataTemplate">
<Grid>
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="#FF3523FF" Offset="0.25"/>
<GradientStop Color="Black" Offset="1"/>
<GradientStop Color="#FF3523FF" Offset="0.75"/>
</LinearGradientBrush>
</Grid.Background>
<TextBlock TextWrapping="Wrap" Text="{Binding NomeCorso}" FontSize="24" FontFamily="Freestyle Script" TextAlignment="Center"/>
</Grid>
</DataTemplate>
And, in the end, the method delete that checks if the list returned by IsolatedStorage is empty, if so, I put a fake empty element in the list bounded to the ListPicker, in order to not receive the error mentioned in this post.
if (this.CorsoSelected != null)
{
---
List<Corsi> corsi = new List<Corsi>(DBHelper.GetCorsiByUserId(PassaggioValori.UserId));
if (corsi.Count > 0)
{
this.ListaCorsiUser = new ObservableCollection<Corsi>(corsi);
}
else
{
this.ListaCorsiUser = new ObservableCollection<Corsi>(new List<Corsi>() { new Corsi()});
}
----
}
The strange thing was that, if the list was empty when the page has been loaded, nothing happens, instead, when I removed the last element from the list, the application raised the exception "SelectedItem must always be set to a valid value".
Now the problem is solved.