DataBinding inside data template in UWP - xaml

I have a usercontrol as follows which has DataTemplate. I want to bind the data inside the DataTemplate to a property inside the DataContext. In Uwp frustratingly they don't have ancestor type, how can I make my thing to work. I have refered this post UWP Databinding: How to set button command to parent DataContext inside DataTemplate but it doesn't work. Please help.
UserControl:
<local:CommonExpanderUserControl>
<local:CommonExpanderUserControl.ExpanderContent>
<DataTemplate x:DataType="Data:VmInstrumentSettingsLocal">
<StackPanel>
<TextBlock Text="{Binding LisLocalSettings.SomeText}"/>
<controls:ButtonBadged x:Name="ButtonApplyLisLocalChanges" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
x:Uid="/Application.GlobalizationLibrary/Resources/InstrumentSettingsViewButtonApply"
HorizontalAlignment="Center"
Margin="8"
Command="{Binding LisLocalSettings.SaveLisSettings}"/>
</StackPanel>
</DataTemplate>
</local:CommonExpanderUserControl.ExpanderContent>
</CommonExpanderUserControl>
In my UserControl xaml.cs as follows. I want to bind the button command to Command property inside the LisLocalSettings, but it won't work.
public InstrumentSetupLocalSettingsView()
{
this.InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty LisLocalSettingsProperty = DependencyProperty.Register(
nameof(LisLocalSettings),
typeof(VmInstrumentSettingsLisLocal),
typeof(InstrumentSetupLocalSettingsView),
new PropertyMetadata(default(VmInstrumentSettingsLisLocal)));
public VmInstrumentSettingsLisLocal LisLocalSettings
{
get => (VmInstrumentSettingsLisLocal) GetValue(LisLocalSettingsProperty);
set => SetValue(LisLocalSettingsProperty, value);
}

DataBinding inside data template in UWP
You could place Command in data source, but if the data source is collection, we need implement multiple command instance. In general, we place the command in current DataContext that could be reused. For the detail steps please refer the following.
<Page.Resources>
<DataTemplate x:Key="HeaderTemplate">
<StackPanel
x:Name="ExpanderHeaderGrid"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
Background="Red"
Orientation="Vertical"
>
<TextBlock x:Name="TextBlockLisSharedSettingsTitle" Text="{Binding}" />
<Button Command="{Binding ElementName=RootGrid, Path=DataContext.BtnCommand}" Content="{Binding}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid x:Name="RootGrid">
<uwpControls:Expander Header="hello" HeaderTemplate="{StaticResource HeaderTemplate}" />
</Grid>
Code Behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
public ICommand BtnCommand
{
get
{
return new CommadEventHandler<object>((s) => BtnClick(s));
}
}
private void BtnClick(object s)
{
}
}
public class CommadEventHandler<T> : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<T> action;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action((T)parameter);
}
public CommadEventHandler(Action<T> action)
{
this.action = action;
}
}

Related

Binding not updating WinUI 3

I'm using WinUI in combination with the microsoft MVVM toolkit.
However im experiencing some issues with Binding and can't figure out where the problem lies.
The ViewModel and models used within the ViewModel are of type observableObject. The Command is fired, and the data is fetched. However the binding is not showing a result in the UI, unless i change the xaml and hot reload the change.
My page:
<Page
x:Class="ThrustmasterGuide.Pages.WheelBasePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ThrustmasterGuide.Pages"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:ThrustmasterGuide.DataAccess.Context.Model"
xmlns:wheelbase="using:ThrustmasterGuide.ViewModel.Wheelbase"
xmlns:xaml="using:ABI.Microsoft.UI.Xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:converters="using:ThrustmasterGuide.Converters"
xmlns:wheelBase="using:ThrustmasterGuide.Model.WheelBase"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance wheelbase:WheelBaseViewModel, IsDesignTimeCreatable=True}">
<Page.Resources>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:InvertBoolToVisibilityConverter x:Key="InvertBoolToVisibilityConverter" />
</Page.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:EventTriggerBehavior.Actions>
<core:InvokeCommandAction Command="{x:Bind ViewModel.LoadWheelBaseCommand}" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel Padding="16 16 16 16" Orientation="Vertical">
<StackPanel>
<TextBlock FontSize="18" Text="{x:Bind ViewModel.WheelBase.Name}" />
<TextBlock FontSize="18" Text="Symptomen:" />
<TextBlock Text="Kies hieronder een symptoom uit om te starten." />
<ProgressRing IsActive="true"
Visibility="{x:Bind ViewModel.LoadWheelBaseCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" />
<TreeView ItemsSource="{x:Bind ViewModel.WheelBase.Symptoms, Mode=OneWay}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="wheelBase:SymptomModel">
<TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Description}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<Button VerticalAlignment="Bottom" Command="{x:Bind ViewModel.LoadWheelBaseCommand}"
Content="Refresh">
</Button>
</StackPanel>
My ViewModel:
public class WheelBaseViewModel : ObservableRecipient
{
public WheelBaseModel WheelBase { get; set; }
public string WheelBaseName { get; set; }
private readonly WheelBaseService _wheelBaseService;
public IAsyncRelayCommand LoadWheelBaseCommand { get; }
public WheelBaseViewModel(WheelBaseService wheelBaseService)
{
_wheelBaseService = wheelBaseService;
LoadWheelBaseCommand = new AsyncRelayCommand(FetchWheelBase);
}
public async Task FetchWheelBase()
{
WheelBase = await _wheelBaseService.GetWheelBase(WheelBaseName);
}
}
My model:
namespace ThrustmasterGuide.Model.WheelBase
{
public class WheelBaseModel : ObservableObject
{
public string Name { get; set; }
public ObservableCollection<SymptomModel> Symptoms { get; set; }
}
}
My Code behind:
public sealed partial class WheelBasePage : Page
{
public WheelBasePage()
{
this.InitializeComponent();
this.DataContext = App.Current.Services.GetService<WheelBaseViewModel>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.ViewModel.WheelBaseName = e.Parameter as string;
}
public WheelBaseViewModel ViewModel => (WheelBaseViewModel)DataContext;
}
What is it that i missed to make the UI bind to the WheelBaseModel values?
Update I added mode=OneWay, but still not updating.
Should it be noted that im showing pages within a content frame after navigation?
{x:Bind} has a default mode of OneTime, unlike {Binding}, which has a default mode of OneWay.
I believe you need to use SetProperty so it's known when to raise such events?
https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/observableobject
namespace ThrustmasterGuide.Model.WheelBase
{
public class WheelBaseModel : ObservableObject
{
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
public ObservableCollection<SymptomModel> Symptoms { get; set; }
}
and also bind mode = OneWay
<TextBlock FontSize="18" Text="{x:Bind ViewModel.WheelBase.Name, Mode=OneWay}" />
No indication that your properties notify of their changes.
https://learn.microsoft.com/dotnet/api/system.componentmodel.inotifypropertychanged

Passing additional arguments to user control inside the data template

This is the xaml code what i am using
<GridView
Grid.Row="0"
x:Name="RootGrid"
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding RootListSource}">
<GridView.ItemTemplate>
<DataTemplate>
<UserControl:TreeInfoControl/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
In this my user control, it contain another GridView that holds a different IEnumerable collection. What i am trying to achieve is i need to pass this collection through code. I tried this by adding a dependency property to the treecontrol but it is not working. So i am looking for a solution that enable passing the collection through xaml (somehow through the user control). I know it is possible to add that collection to my existing collection and bind that one. But for now i can't use that method.
Here's how you do it.
Start with your App.xaml so we can reuse the demo template
<Application.Resources>
<DataTemplate x:Key="MyContentControl">
<Grid Height="100" Width="100" Background="Maroon">
<TextBlock Text="{Binding FallbackValue=0}" Foreground="White" FontSize="40" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</Application.Resources>
Then we can define your user control
<d:UserControl.DataContext>
<local:MyControlViewModel Number="-1" Letter="~K" />
</d:UserControl.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
<ContentControl Content="{Binding Number}"
ContentTemplate="{StaticResource MyContentControl}" />
<ListView ItemsSource="{Binding Letters}" IsHitTestVisible="False"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
</StackPanel>
And then we can define your MainPage.xaml
<Page.DataContext>
<local:MainPageViewModel Letter="C" />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView x:Name="MyList" ItemsSource="{Binding Letters}"
ItemTemplate="{StaticResource MyContentControl}"
SelectedItem="{Binding Letter, Mode=TwoWay}" />
<ListView Grid.Column="1" ItemsSource="{Binding Numbers}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:MyControlViewModel
x:Key="MyDataContext" Number="{Binding}"
Letters="{Binding ItemsSource, ElementName=MyList}"
Letter="{Binding SelectedItem, ElementName=MyList}" />
</StackPanel.Resources>
<local:MyControl DataContext="{StaticResource MyDataContext}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Nothing special yet, right? Well, not so fast. We're creating the viewmodel for the user control , setting the properties of the view model from the surrounding scope, then passing it in to the DataContext of the user control explicitly. Cool, huh? Simple enough, if you think about it. Want to set those properties inside the tag? Sure you do. But you can't. The order of operation would be all wrong. You'll just have to trust me.
Now, there's ZERO code behind for your user control. But the view model looks like this:
public class MyControlViewModel : BindableBase
{
public int Number
{
get { return (int)GetValue(NumberProperty); }
set
{
SetValue(NumberProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(int), typeof(MyControlViewModel),
new PropertyMetadata(0, (s, e) => { }));
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
public ObservableCollection<string> Letters
{
get { return (ObservableCollection<string>)GetValue(LettersProperty); }
set
{
SetValue(LettersProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LettersProperty =
DependencyProperty.Register("Letters", typeof(ObservableCollection<string>),
typeof(MyControlViewModel),
new PropertyMetadata(new ObservableCollection<string>(new[] { "~W", "~X", "~Y", "~Z" }), (s, e) => { }));
}
All the properties are dependency properties. I hope you noticed. I didn't just do that because I like to type. Though I do like to type. Fact is, I did that because in order to have internal binding you must use a dependency property - and a dependency property that raises property changed! That last part isn't trivial. But does it have to be in a view model? No. But I like it that way.
You might reference this: http://blog.jerrynixon.com/2013/07/solved-two-way-binding-inside-user.html
There's also no code behind for your MainPage. But the view model looks like this:
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
this._Letters = new ObservableCollection<string>(new[] { "A", "B", "C", "D" });
this._Numbers = new ObservableCollection<int>(new[] { 1, 2, 3, 4 });
}
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
base.RaisePropertyChanged();
}
}
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(MyControlViewModel),
new PropertyMetadata("Z", (s, e) => { }));
ObservableCollection<string> _Letters = new ObservableCollection<string>();
public ObservableCollection<string> Letters { get { return _Letters; } }
ObservableCollection<int> _Numbers = new ObservableCollection<int>();
public ObservableCollection<int> Numbers { get { return _Numbers; } }
}
The bindable base is standard, here's the code for it:
public abstract class BindableBase : DependencyObject, System.ComponentModel.INotifyPropertyChanged
{
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
When it's all done, you should get exactly what you want. Something like this:
Not to over-simplify things. But, it's that easy.
Look, getting your head wrapped around XAML is not always easy when you start to nest contexts. I don't blame you for not getting it on first run. But I hope this helps you get started. Keep pushing
Best of luck!

XAML ListBox displays class name instead of class property

Here I have a Listbox configured where the TextBlox in the DataTemplate is set to bind the "Name" Property. But instead it shows the full class name "DomainClasses.Entities.Program". Why?
<Grid DataContext="{Binding _CurrentProgram }">
.....
.....
<ListBox x:Name="ProgramsListBox" Width="600" Height="400" Margin="50,0,50,0" ItemsSource="{Binding _Programs}" VerticalAlignment="Top">
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<DataTemplate>
</ListBox>
----
----
</Grid>
This is the ViewModel class
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
_currentProgram = new Program();
_Programs = new ObservableCollection<Program>();
}
public async void SaveProgram(bool isEditing)
{
_Programs.Add(_currentProgram);
OnPropertyChanged();
}
private Program _currentProgram;
public Program _CurrentProgram
{
get { return _currentProgram; }
set
{
_currentProgram = value;
OnPropertyChanged();
}
}
private ObservableCollection<Program> _programs;
public ObservableCollection<Program> _Programs
{
get
{
return _programs;
}
set
{
this._programs = value;
OnPropertyChanged();
}
}
// Implement INotifyPropertyChanged Interface
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
This is what you need:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Button/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Noticed the ListBox.ItemTemplate around the DataTemplate.
What you have:
<ListBox x:Name="ProgramsListBox" Width="600" Height="400" Margin="50,0,50,0" ItemsSource="{Binding _Programs}" VerticalAlignment="Top">
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<DataTemplate>
</ListBox>
Creates a ListBox with a DataTemplate as a child (in the same sense that the items in the ItemsSource are children of the ListBox). If I remember correctly, when you set the ItemsSource of a ListBox, all items set in the other fashion are removed. So what you're ending up with is a ListBox with a bunch of Programs in it, which no ItemsTemplate set, so it simply shows the name of the bound class.
You need to add the data template inside listview.itemtemplate and then do the binding. Right now you are adding the data template as a child of the listview.

Get SelectedItem from ListBox

I want to get the SelectedItem from a ListBox which looks like this inside my Windows 8 Store App:
<ListBox x:Name="listBox" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />
The problem is, that the ListBox don't fire the SelectedItem propertie. I have to use IsSynchronizedWithCurrentItem="True" but then an error appears which says the true isn't supportet for this property. What do I have to do or are there any other ways to get the SelectedItem propertie?
I have this Code behind:
namespace ExampleApp
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private object currentItem;
//Constructor and so on
public object SelectedItem
{
get { Debug.WriteLine("get"); return currentItem; }
set { Debug.WriteLine("set"); currentItem = value; NotifyPropertyChanged(); }
}
}
}
you should try this
<ListBox x:Name="listBox" SelectedItem="{Binding ElementName=YourPageName,path=DataContext.SelectedItem, Mode=TwoWay}" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />

Windows Phone 7 XAML -- getting a binding to work with the container of my object

What I want to do is bind the text of a TextBlock to my custom ButtonSymbol property of the UserControl.
Here is the XAML for the UserControl. The Binding part for the TextBlock needs to be filled in.
<UserControl
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Calculator.CalculatorButton"
d:DesignWidth="120" d:DesignHeight="80">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Image Source="buttonb#2x.png" Stretch="Fill"/>
<Button x:Name="InvisibleButton" Content="{Binding ButtonSymbol}" Margin="0,0,0,0" d:LayoutOverrides="Width, Height" BorderThickness="1" Click="InvisibleButton_Click"/>
<TextBlock HorizontalAlignment="Center" Margin="0,0,0,0" TextWrapping="Wrap"
Text="{Binding ????????}"
VerticalAlignment="Top"/>
</Grid>
</UserControl>
And here is the CodeBehind:
namespace Calculator
{
public partial class CalculatorButton : UserControl
{
public string ButtonSymbol {get; set;}
public CalculatorButton()
{
// Required to initialize variables
InitializeComponent();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
// TODO: Add event handler implementation here.
}
private void InvisibleButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
Debug.WriteLine(#"click");
Debug.WriteLine(ButtonSymbol);
// TODO: Add event handler implementation here.
}
}
}
Note that this is WP7 with Silverlight, and the RelativeSource class is not the same as in other versions.
You need to set the DataContext of the user control.
If you add this:
this.DataContext = this;
into the constructor or Loaded event of the user control you can then do this:
Text="{Binding ButtonSymbol}"
Note that you can also declaratively bind the DataSource of the XAML, this is just an easy programmatic way to do it.