Refresh ListView item on property changed - c#

I have a listView like this :
<ListView Grid.Row="1" x:Name="itemsListView"
BorderBrush="White" HorizontalAlignment="Stretch"
ItemsSource="{Binding Path=Items}"
SelectedItem="{Binding Path=ActualItem}">
<ListView.View>
<GridView>
<GridViewColumn Header="{x:Static p:Resources.STATUS}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<local:StatusElement State="{Binding Path=Status,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Height="20"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:Resources.NAME}"
DisplayMemberBinding="{Binding Path=Name,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
</GridView>
</ListView.View>
</ListView>
It binds a list of items named Items with many fields. A thread parse the items and update the fields when it finishes. I call method OnPropertyChanged when fields are updated. It works fine for all fields except the one using my UserControl local:StatusElement. I've try to display my STATUS like the NAME, it's refreshes correctly but with local:StatusElement there is no refresh. breakpoints on get/set for StatusElement.State are never reached.
My userControl :
<UserControl ...
x:Name="mainControl">
<Grid Name="LabelGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Name="MyImage"
Source="{Binding Source, Source={StaticResource MyImage}}"
Width="{Binding Height, ElementName=mainControl}"
Height="{Binding Height, ElementName=mainControl}"/>
<Label Grid.Column="1" Name="statusLabel"/>
</Grid>
</UserControl>
and:
public partial class StatusElement : UserControl
{
// Dependency property backing variables
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State",
typeof(String), typeof(StatusElement), new UIPropertyMetadata(null));
private string _state = "";
public String State
{
get
{
return _state;
}
set
{
_state = value;
RefreshState();
}
}
private void RefreshState()
{
switch (State)
{
case "":
MyImage.Visibility = Visibility.Hidden;
break;
default:
MyImage.Visibility = Visibility.Visible;
break;
}
statusLabel.Content = State;
}
public StatusElement()
{
InitializeComponent();
RefreshState();
}
}
Why the Content of my statusLabel doesn't refresh ?

Your definition of the State dependency property is wrong.
It has to look like shown below, where the CLR property wrapper must call the GetValue and SetValue methods of the DependencyObject that owns the property.
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
"State",
typeof(string),
typeof(StatusElement),
new PropertyMetadata(null, (o, e) => ((StatusElement)o).RefreshState()));
public string State
{
get { return (string)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
Note the second argument to the PropertyMetadata constructor. It is a static PropertyChangedCallback, implemented as lambda expression.

Your class doesnt implement the INotifyPropertyChanged event. Implement it for the update to happen.
Notifies clients that a property value has changed.
public partial class StatusElement : UserControl,INotifyPropertyChanged
{
....
public event PropertyChangedEventHandler PropertyChanged;
private void RefreshState([CallerMemberName]string prop = "")
{
switch (State)
{
case "":
MyImage.Visibility = Visibility.Hidden;
break;
default:
MyImage.Visibility = Visibility.Visible;
break;
}
statusLabel.Content = State;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}

Related

How do I create a reusable UserControl and Bind a Command to it?

I'm trying to bind a reusable button in a carusel, what I want to achieve is a add lets say 6 buttons each button will have a command that according to button name will navigate to the proper page.
I can do that by doing this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<Button Command="{x:Bind ViewModel.NavigateToPage1, Mode=OneWay}"
Content="{x:Bind ViewModel.Name, Mode=OneWay}"/>
</toolkitcontrols:Carousel>
If I do that, I'll be adding 5 more buttons and i'll have to write properties for each button.
So instead I want to use a UserControl and just write something like this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="data:ButtonInfo">
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
But I've failed doing it, I found out some tutorial but all as I understand will make me write this like of code:
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
like 6 times, and i dont know how it will take x:DataType of DataTemplate for my list of properties.
This is my UserControl.xaml.cs
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public ButtonInfo ButtonInfo => (DataContext as ButtonInfo);
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
Loaded += NavigationMenuButtonTemplate_Loaded;
}
private void NavigationMenuButtonTemplate_Loaded(object sender, RoutedEventArgs e)
{
Bindings.Update();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
this is my ButtonInfo.cs
public class ButtonInfo
{
public string Symbol { get; set; }
public string FontFamily { get; set; }
public string MenuName { get; set; }
public string BenefitKind { get; set; }
public string Status { get; set; }
}
and this is my UserControl.xaml
<Button x:Name="NavigationMenuTemplate"
Width="300"
Height="300"
Command="{Binding NavigateToPageCommand, ElementName=root, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NavigationMenuIconTextBlock"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
FontFamily="{x:Bind ButtonInfo.FontFamily, Mode=OneWay, FallbackValue='Webdings'}"
Text="{x:Bind ButtonInfo.Symbol, Mode=OneWay, FallbackValue='‘'}"
FontSize="150"
Foreground="Black"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Text="{x:Bind ButtonInfo.MenuName, Mode=OneWay, FallbackValue='CALCULADORA JORNADAS EXTRAORDINARIAS'}"
FontSize="12"
Foreground="Black"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Grid.Row="2"
Grid.Column="0"
Text="{x:Bind ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Left"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Grid.Row="2"
Grid.Column="1"
Text="{x:Bind ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Right"/>
</Grid>
</Button>
can somebody help me and point me in the right direction please.
what am I missing?
The ItemTemplate approach in your question is actually on the right track.
In the end, your XAML would look something similar to the following(only a few properties are included but you get the idea) -
<toolkitcontrols:Carousel ItemsSource="{x:Bind ButtonInfoCollection}">
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="local:ButtonInfo">
<local:NavigationMenuButton NavigateToPageCommand="{Binding DataContext.NavigateToPageCommand, ElementName=MyPageName}"
NavigateToPageCommandParameter="{x:Bind PageType}"
MenuName="{x:Bind MenuName}"
SymbolPath="{x:Bind Symbol}" />
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
With the structure above in mind, you just need to expose these properties as dependency properties in your NavigationMenuButton user control. See below as a simple example -
NavigationMenuButton XAML
<UserControl x:Class="DesignTest.NavigationMenuButton">
<!--If any of the properties can be updated, change the binding Mode to OneWay-->
<Button Command="{x:Bind NavigateToPageCommand, Mode=OneWay}" CommandParameter="{x:Bind NavigateToPageCommandParameter}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image x:Name="SymbolImage" Stretch="UniformToFill" />
<TextBlock Text="{x:Bind MenuName, FallbackValue='JORNADAS EXTRAORDINARIAS', TargetNullValue='JORNADAS EXTRAORDINARIAS'}" Grid.Column="1" />
</Grid>
</Button>
</UserControl>
NavigationMenuButton Code-behind
public sealed partial class NavigationMenuButton : UserControl
{
public NavigationMenuButton()
{
InitializeComponent();
}
public ICommand NavigateToPageCommand
{
get => (ICommand)GetValue(NavigateToPageCommandProperty);
set => SetValue(NavigateToPageCommandProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandProperty = DependencyProperty.Register(
"NavigateToPageCommand", typeof(ICommand), typeof(NavigationMenuButton), new PropertyMetadata(null));
public object NavigateToPageCommandParameter
{
get => GetValue(NavigateToPageCommandParameterProperty);
set => SetValue(NavigateToPageCommandParameterProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandParameterProperty = DependencyProperty.Register(
"NavigateToPageCommandParameter", typeof(object), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string MenuName
{
get => (string)GetValue(MenuNameProperty);
set => SetValue(MenuNameProperty, value);
}
public static readonly DependencyProperty MenuNameProperty = DependencyProperty.Register(
"MenuName", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string SymbolPath
{
get => (string)GetValue(SymbolPathProperty);
set => SetValue(SymbolPathProperty, value);
}
public static readonly DependencyProperty SymbolPathProperty = DependencyProperty.Register(
"SymbolPath", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null, (s, e) =>
{
// We don't do the x:Bind for this property in XAML because the Image control's Source property
// doesn't accept a string but a BitmapImage, so one workaround is to do the conversion here.
var self = (NavigationMenuButton)s;
var image = self.SymbolImage;
var symbolPath = (string)e.NewValue;
image.Source = new BitmapImage(new Uri(self.BaseUri, symbolPath ?? "/Assets/default_path"));
}));
}
Note you will need to include a PageType property in your ButtonInfo class for navigation purpose.
public Type PageType { get; set; }
I personally don't like having a navigation command defined at the item level (i.e in the ButtonInfo class), instead, I use an ElementName binding in the Carousel's data template to search up a level and bind to the NavigateToPageCommand defined in the page's DataContext, which is the page's ViewModel.
This means that this ViewModel will have both ButtonInfoCollection and NavigateToPageCommand defined like below -
public ObservableCollection<ButtonInfo> ButtonInfoCollection { get; } = new ObservableCollection<ButtonInfo>
{
new ButtonInfo { MenuName = "New Menu", PageType = typeof(BlankPage1), Symbol = "/Assets/StoreLogo.png" }
};
public DelegateCommand<Type> NavigateToPageCommand { get; } = new DelegateCommand<Type>(type =>
App.Frame.Navigate(type));
I hope this all makes sense. Good luck!
Ok, Firstable to Dilmah its always posible to build a reusable usercontrol within any itemTemplate. And I'll show you how right now and right here.
I have come up with two solution the first solution its inspire in what I was looking for after reading a lot about databing {x:Bind} and {Binding} markup, I was able to learn how to create a reusable UserControlTemplate
SOLUTION No.1
First I'll show you how to create a Navigation menu with a carusel control, which can be found at nuget package Microsoft.Toolkit.Uwp.UI.Control
so this is my code now on my MainMenuPage reference to carusel control:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.NavMenuButtonVMs}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate>
<usercontrolvm:NavigationMenuButtonTemplate/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
this important part of this code is at ItemSource property which is x:Bind to my NavMenuButtonVms ObservableCollection, and my Usercontrol which is wrapped withing Carousel.ItemTemplate and DataTemplate tags which will allows us to be able to reuse our code and create a N number of controls within our list.
the next is my ViewModel for my MainMenuPage:
public class MainMenuPageViewModel : Mvvm.ViewModelBase
{
ObservableCollection<NavigationMenuButtonTemplateViewModel> _NavMenuButtonVMs = default(ObservableCollection<NavigationMenuButtonTemplateViewModel>);
public MainMenuPageViewModel()
{
Shell.HamburgerMenu.IsFullScreen = false;
NavMenuButtonVMs = GetNavMenuButtonInfo();
}
public string Title => GetLocalizeString("MainMenuPageViewModelTitle");
public ObservableCollection<NavigationMenuButtonTemplateViewModel> NavMenuButtonVMs
{
get { return _NavMenuButtonVMs; }
private set { Set(ref _NavMenuButtonVMs, value); }
}
public override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
NavigationService.ClearHistory();
return base.OnNavigatedToAsync(parameter, mode, state);
}
}
As you can see I initialize my ObservableCollection within my constructor.
The method GetNavMenuButton() is a static class in a Helpers Namespace but i'll show you the code so you can have an idea of how to seed the list also you can notice that I'm not calling the static class that because i'm using C# 6.0 syntax where you can call directly static methods within your class.
you can add a using statement for static classes like this one:
using static Ceneam.Helpers.NavigationMenuButtonViewModelHelper;
this statement allows you to use a static method like this:
GetNavMenuButtonInfo();
instead of this:
NavigationMenuButtonViewModelHelper.GetNavMenuButtonInfo();
I explained this in case you dont understand my code.
then I'll create my usercontrol which I'll show you the xaml, xaml.cs and also the viewmodel.
pay attention to the binding markup at usercontrol since you'll have to quit using x:Bind within a reusable usercontrol.
This is my NavigationMenuButtonTemplate.xaml
<UserControl
x:Class="Ceneam.UserControlViews.NavigationMenuButtonTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Ceneam.UserControlViews"
xmlns:vm="using:Ceneam.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="400"
d:DesignWidth="400">
<Grid VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="NavigationMenuTemplate"
Command="{Binding NavigateToPageCommand, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="NavigationMenuIconImage"
Source="{Binding ButtonInfo.Symbol, Mode=OneWay, FallbackValue='ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png'}"
PointerEntered="NavigationMenuIconImage_PointerEntered"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Text="{Binding ButtonInfo.MenuName, Mode=OneWay, FallbackValue='JORNADAS EXTRAORDINARIAS'}"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Text="{Binding ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Text="{Binding ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"/>
</Grid>
</Button>
</Grid>
as you can see I'm using binding markup only and the reason is because I use a viewmodel with a parameter that right i had to create a dependency on my usercontrol:
public class NavigationMenuButtonTemplateViewModel : Mvvm.ViewModelBase
{
ButtonInfo _ButtonInfo = default(ButtonInfo);
public NavigationMenuButtonTemplateViewModel() { }
public NavigationMenuButtonTemplateViewModel(ButtonInfo buttonInfo)
{
ButtonInfo = new ButtonInfo
{
BenefitKind = buttonInfo.BenefitKind,
Status = buttonInfo.Status,
MenuName = buttonInfo.MenuName,
Symbol = buttonInfo.Symbol
};
}
public ButtonInfo ButtonInfo
{
get { return _ButtonInfo; }
set { Set(ref _ButtonInfo, value); }
}
public DelegateCommand NavigateToPageCommand => new DelegateCommand(async () => { await ExecuteNavigateToPageCommand(); });
private async Task ExecuteNavigateToPageCommand()
{
var message = new MessageDialog("Test");
await message.ShowAsync();
}
}
since you create a contructor with a parameter in the viewmodel I wasnt able to create a strongly type bind with that constructor that is the main reason that made me leave behind x:bind markup for my usercontrol that means that you cant use x:bind methods on events. you will have to use styling methods within the xaml.cs file of your usercontrol.
If you declare in you xaml something like this:
<UserControl.DataContext>
<vm:usercontrol x:Name=ViewModel/>
<UserControl.DataContext>
it will always trigger your parameterless constructor getting rid of your init values of even worse getting NullReferenceExceptions, also you could use DataContext for DesignTime Data but it has to be declare at the beginning of your file and I wont center on it here.
and finally at my static class is where I create my UC (usercontrol) with a parameter in them this is my static class:
public static class NavigationMenuButtonViewModelHelper
{
public static ObservableCollection<NavigationMenuButtonTemplateViewModel> GetNavMenuButtonInfo()
{
var data = new ObservableCollection<NavigationMenuButtonTemplateViewModel>();
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/SatSunBonusWhite256x256.png",
MenuName = "PRIMAS SABATINAS Y DOMINICALES",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png",
MenuName = "JORNADAS EXTRAORDINARIAS",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/VacationBonusWhite256x256.png",
MenuName = "PRIMA VACACIONAL",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PecoWhite256x256.png",
MenuName = "PERMISOS ECONOMICOS",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PunctualityBonusWhite256x256.png",
MenuName = "INCENTIVO PUNTUALIDAD Y ASISTENCIA",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/BonForSeniorityWhite256x256.png",
MenuName = "BONO DE ANTIGUEDAD",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/WageIncreaseWhite256x256.png",
MenuName = "RETROACTIVO SUELDO",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
return data;
}
private static void AddNavMenuButtonItem(ObservableCollection<NavigationMenuButtonTemplateViewModel> data, NavigationMenuButtonTemplateViewModel item)
{
data.Add(item);
}
}
also if you wish to style properties programatically you should do it at xaml.cs like for example like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
private void NavigationMenuIconImage_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var image = (Image)sender;
var bitmapImage = image.Source as BitmapImage;
var uri = bitmapImage?.UriSource;
var uriPath = uri?.AbsolutePath;
var newUriPath = $#"ms-appx://{uriPath.Replace("White", "Black")}";
image.Source = new BitmapImage(new Uri(newUriPath, UriKind.RelativeOrAbsolute));
}
}
**SOLUTION No.2: **
another solution could be using usercontrols with dependency properties like this one:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemSource="{x:Bind ViewModel.MenuList}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<usercontrolvm:NavigationMenuButtonTemplate ButtonInfo="{x:Bind ViewModel.MenuList[0],Mode=OneWay}"
NavigateToPageCommand = "{x:Bind ViewModel.NavigateToPageCommand}"/>
You will have to create a NavigationMenuButtonTemplate.xaml.cs with dependency properties like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
public ButtonInfo ButtonInfo
{
get { return (ButtonInfo)GetValue(ButtonInfoProperty); }
set { SetValue(ButtonInfoProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonInfoProperty =
DependencyProperty.Register("ButtonInfo",
typeof(ButtonInfo),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
I dont like this solution because I'll have to repeat code at xaml file but its a good choice too.
Hope you like my answer I think it can be used by many of us and its appliable to many other controls with your endless imagination.

Getting the Tag value of a Control

Based from this XAML,
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBlock Text="{Binding ProductDescription}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox x:Name"txtExchange"
Tag="{Binding ProductBarcode}"/>
<Button Content="Add"
Tag="{Binding ProductBarcode}"
Click="SelectExchangeProduct" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
since all rows inserted will have the same TextBox, using x:Name to get the value is impossible.
I added binding to the Tag property so that I can differentiate all the TextBox. The problem is how can I find that specific TextBox? I tried using FindName(), but I don't know how to get that TextBox with a specific Tag value.
I also tried this:
var txtExchangeQuantity = someParentControl.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();
but also didn't work. I saw potential to that Where part, but don't know how to implement it.
Here is the Click event:
private void SelectExchangeProduct(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
string barcode = btn.Tag.ToString();
var txtExchangeQuantity = grdExchange.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();
}
Here is the object that I'm binding to (using ObservableCollection):
class TransactionList{
private string _productBarcode;
public string ProductBarcode
{
get { return _productBarcode; }
set { _productBarcode = value; }
}
private string _productDescription;
public string ProductDescription
{
get { return _productDescription; }
set { _productDescription = value; }
}
}
Create an implementation of ICommand somewhere in your code like this:
public class RelayCommand : ICommand
{
#region Fields
private Action<object> execute;
private Predicate<object> canExecute;
#endregion
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute ?? throw new ArgumentException(nameof(execute));
this.canExecute = canExecute ?? throw new ArgumentException(nameof(canExecute));
}
#endregion
#region Methods
public void InvokeExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
#endregion
}
You can then implement a property in your view model like
public RelayCommand SelectExchangeProductCommand { get; private set; }
public ViewModel()
{
SelectExchangeProductCommand = new RelayCommand(SelectExchangeProductExecute, SelectExchangeProductCanExecute);
}
and then have the two methods implemented:
public void SelectExchangeProductExecute(object parameter)
{
// parameter will then be your Barcode
if(parameter is string barcode)
{
// Now you have the barcode available... but you can really bind anything to end up here.
}
}
public bool SelectExchangeProductCanExeucte(object parameter)
{
// return true and false accordingly to execution logic. By default, just return true...
return true;
}
Then you can bind the Button to this command in xaml like the following:
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox x:Name"txtExchange" />
<Button Content="Add"
Command="{Binding Source=ViewModel, Path=SelectExchangeProductCommand}"
CommandParameter="{Binding ProductBarcode}" />
</StackPanel>
</DataTemplate>
And like this you do not need to identify the Button or TextBox, just bind the respective data to CommandParameter and the template will do the rest.
Seems ICommand is the way to go but this is a new concept to me so I'll tell you what would I do in your case:
Group controls in a UserControl "ExchangeControl" with eg. a TextBox "txtExchange", a Button "btnAdd" and an event so when you click btnAdd, you'll have full control to do something with txtExange.
Instantiate it when needed:
someStackPanel.Children.Add(new ExchangeControl() {Content = "SOME TEXT"});
To let the above code work, you should have this property in your ExchangeControl class:
private string content;
public string Content
{
get { return content; }
set { content = value; txtExchange.Text = value; }
}
So... now you can get txtExchange content (SOME TEXT) by clicking btnAdd if you created an event to the button click in your ExchangeControl class
Just to graph, here's an example of adding an event to your button assuming there's already a StackPanel with some ExchangeControl items
someStackPanel.Children.Cast<ExangeControl>().ToList().ForEach(ec =>
ec.btnAdd.Click += (se, a) => MessageBox.Show(ec.txtExchange.Text));
By the way, here's how to get content of specific TextBox in a DataGrid when selected:
string someContent = (yourDataGrid.SelectedItem as TextBox).Text;
It is easier by using VisualTreeHelper to get Children by type.
How to get children of a WPF container by type?

C# Wpf Binding to Collection of Userdefined Class not working as expected

Currently i have an ObservableCollection of MyClass in my Viewmodel. I use the getter of this Collection to fill it from an other Datasource. I can now Display this Data in a Window(Grid) and the correct Data is shown, but when i change the Data the set is not fired(I think it is because not the Collection is changed, only a Element in the Collection). Should i create a Property for every Property of MyClass, so i can react to the changes of a single Value, the Questions i ask myself are:
How do i know what Element is selected at the moment
How to fill the Collection correct when i have a binding to every single item
I also thought of a Event when my Collection is changed, but i am not sure how to implement it right.
public ObservableCollection<MyClass<string>> SelectedParams
{
get
{
//Fill the Collection
}
set
{
//I think here i want to react to changed Textboxvalues in my View
}
}
public class MyClass<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private T _curValue;
private string _value1;
private string _value2;
public string Value1
{
get
{
return _value1;
}
set
{
if (_value1 != value)
{
_value1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value1)));
}
}
}
public string Value2
{
get
{
return _value2;
}
set
{
if (_value2 != value)
{
_value2 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value2)));
}
}
}
public T curValue
{
get
{
return _curValue;
}
set
{
_curValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(curValue)));
}
}
public MyClass()
{
}
public MyClass(string val1, string val2, T curVal)
{
Value1 = val1;
Value2 = val2;
curValue = curVal;
}
}
The xaml Code looks something like this
<ItemsControl ItemsSource="{Binding SelectedParams}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Value1}"/>
<Label Grid.Column="1" Content="{Binding Value2}"/>
<TextBox Grid.Column="2" Text="{Binding curValue, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit1: Changed MyClass to INotifyPropertyChanged now the Collection changes internal Values but the Setter is still not called on change of a Value
You need to implement INotifyPropertChanged interface for MyClass and raise the PropertyChanged in setter to notify UI that the property value changed.
How do i know what Element is selected at the moment
If you want support for item selection you have to use an other control. ItemsControl does not support selection.
Use ListView for example. Bind ItemsSource and SelectedItem to your class. Now every time you click on an item, SelectedValue is updated. And if you change SelectedValue from code the UI updates the selected item in the list. You can also bind other controls to SelectedValue like I did with the TextBlock outside the ListView.
View
<StackPanel>
<ListView ItemsSource="{Binding Values}" SelectedItem="{Binding SelectedValue}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Item1}" />
<TextBlock Text="=" />
<TextBlock Text="{Binding Path=Item2}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal">
<TextBox Text="Selected:" Background="DarkGray" />
<TextBox Text="{Binding SelectedValue.Item1, Mode=OneWay}" Background="DarkGray" />
</StackPanel>
</StackPanel>
Data
public class ListViewBindingViewModel : INotifyPropertyChanged
{
private Tuple<string,int> _selectedValue;
public ObservableCollection<Tuple<string,int>> Values { get; }
public Tuple<string, int> SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedValue)));
}
}
public ListViewBindingViewModel()
{
Values = new ObservableCollection<Tuple<string, int>> {Tuple.Create("Dog", 3), Tuple.Create("Cat", 5), Tuple.Create("Rat",1)};
}
}

ListView SelectedItems binding: why the list is always null

I'm developing a UWP App, with Mvvm Light and Behaviours SDK. I defined a multi selectable ListView:
<ListView
x:Name="MembersToInviteList"
IsMultiSelectCheckBoxEnabled="True"
SelectionMode="Multiple"
ItemsSource="{Binding Contacts}"
ItemTemplate="{StaticResource MemberTemplate}">
</ListView>
I'd like, with a button binded to a MVVM-Light RelayCommand, to obtain a list with the selected items:
<Button
Command="{Binding AddMembersToEvent}"
CommandParameter="{Binding ElementName=MembersToInviteList, Path=SelectedItems}"
Content="Ok"/>
The RelayCommand (of MVVM-Light framework):
private RelayCommand<object> _addMembersToEvent;
public RelayCommand<object> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<object>(
(selectedMembers) =>
{
// Test
// selectedMembers is always null!
}));
}
}
I put a breakpoint inside the command, and I notice that selectedMembers is always null, although I select various items. By the console output I don't see any binding error or something else.
Also, if I pass as CommandParameter the whole list, and I put a breakpoint inside command's definition, i notice that I can't access to SelectedItems nor SelecteRanges value.
<DataTemplate x:Name="MemberTemplate">
<Viewbox MaxWidth="250">
<Grid Width="250"
Margin="5, 5, 5, 5"
Background="{StaticResource MyLightGray}"
BorderBrush="{StaticResource ShadowColor}"
BorderThickness="0, 0, 0, 1"
CornerRadius="4"
Padding="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0"
Width="45"
Height="45"
Margin="5,0,5,0"
VerticalAlignment="Center"
CornerRadius="50">
<Grid.Background>
<ImageBrush AlignmentX="Center"
AlignmentY="Center"
ImageSource="{Binding Image.Url,
Converter={StaticResource NullGroupImagePlaceholderConverter}}"
Stretch="UniformToFill" />
</Grid.Background>
</Grid>
<TextBlock Grid.Column="1"
Margin="3"
VerticalAlignment="Center"
Foreground="{StaticResource ForegroundTextOverBodyColor}"
Style="{StaticResource LightText}"
Text="{Binding Alias}" />
</Grid>
</Viewbox>
</DataTemplate>
What's the reason? How can I obtain such list?
One of the solutions to pass SelectedItems from ListView in ViewModel (with RelayCommands) is described in igralli's blog.
Pass ListView SelectedItems to ViewModel in Universal apps
Try the following code to get the selected objects from the parameter.
private RelayCommand<IList<object>> _addMembersToEvent;
public RelayCommand<IList<object>> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<IList<object>>(
selectedMembers =>
{
List<object> membersList = selectedMembers.ToList();
}));
}
}
I've made a small example for your case without MVVM-Light and it works perfect.
Maybe the problem is within the RelayCommand-class. I've written the following:
public class RelayCommand<T> : ICommand
{
private readonly Action<T> execute;
private readonly Predicate<T> canExecute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null )
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
return true;
return canExecute((T) parameter);
}
public void Execute(object parameter)
{
execute((T) parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
Thanks to Roman's answer I figured out how to solve the issue:
First of all, as Roman suggested:
private RelayCommand<IList<object>> _addMembersToEvent;
public RelayCommand<IList<object>> AddMembersToEvent
{
get
{
return _addMembersToEvent
?? (_addMembersToEvent = new RelayCommand<IList<object>>(
selectedMembers =>
{
List<object> membersList = selectedMembers.ToList();
}));
}
}
Then, the XAML:
<Button
Command="{Binding AddMembersToEvent}"
CommandParameter="{Binding ElementName=MembersToInviteList, Converter={StaticResource ListViewSelectedItemsConverter}}"
Content="Ok"/>
The difference here is that I passed the whole list as parameter, not it's SelectedItems property. Then, using an IValueConverter I converted from the ListView object to IList<object> of SelectedMember:
public class ListViewSelectedItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var listView = value as ListView;
return listView.SelectedItems;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
In this way the RelayCommand<IList<object>> got the right list and not a null value.
I can't find a reason but you can easily skirt the issue altogether by having an "IsSelected" property on your Contact object and putting a check box on your template:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay}"></CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Row="1" Text="{Binding SelectedItemsOutput}"></TextBlock>
<Button Grid.Row="2" Content="What is checked?" Command="{Binding GoCommand}"></Button>
</Grid>
and VMs etc:
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedItemsOutput;
public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact> { new Contact() { Id = 1, Name = "Foo" }, new Contact() { Id = 2, Name = "Bar" } };
public ICommand GoCommand => new RelayCommand(Go);
public string SelectedItemsOutput
{
get { return _selectedItemsOutput; }
set
{
if (value == _selectedItemsOutput) return;
_selectedItemsOutput = value;
OnPropertyChanged();
}
}
void Go()
{
SelectedItemsOutput = string.Join(", ", Contacts.Where(x => x.IsSelected).Select(x => x.Name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Contact : INotifyPropertyChanged
{
private bool _isSelected;
public int Id { get; set; }
public string Name { get; set; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value == _isSelected) return;
_isSelected = value;
OnPropertyChanged();
}
}
public override string ToString()
{
return Name;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new MainViewModel();
}
}
Just my five cents and might be an absolutely long shot, but you should check this:
If the ItemsSource implements IItemsRangeInfo, the SelectedItems collection is not updated based on selection in the list. Use the SelectedRanges property instead.
I am just guessing based on Tomtom's answer, since he says he did almost exactly as you and got a working solution. Maybe the difference is in this little detail. I haven't checked it myself yet.

How do I bind a custom UserControl to the current item inside a ItemTemplate?

I have a ListBox that is data bound to an ObservableCollection.
Inside the DataTemplate, I have this custom user control that I want to bind to the current item.
How do I do that?
What should the binding path be?
Model:
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items
{
get { return _items; }
set
{
if (_items.Equals(value))
return;
_items = value;
RaisePropertyChanged("Items");
}
}
XAML:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" Text="{Binding Id}" Margin="2"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Name}" Margin="2"/>
<uc:MyControl MyValue="{Binding <WHATSHOULDTHISBE>, Mode=TwoWay}" Margin="2"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
User control:
public partial class MyControl : UserControl
{
public MyItem MyValue
{
get { return (MyItem)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
public static readonly DependencyProperty MyProperty = DependencyProperty.Register("MyValue",
typeof(MyItem), typeof(MyControl),
new PropertyMetadata(
new PropertyChangedCallback(PropertyChanged)));
public MyControl()
{
InitializeComponent();
}
private static void PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyControl c = obj as MyControl;
if (c != null)
{
// TODO: do something here
}
}
}
MyControl.DataContext property would already be bound to your item view model in this case.
Your MyItem is the data context - if you want to bind to some member of My Item then its:
Binding Path=my_item_member
To bind to MyItem in its entirety I think you want:
Binding Path=.
Your might also need a DataTemplate for your control (in addition to the you have for the ListBoxItems) that maps the MyItems members to your control fields.

Categories