UWP DataContext from DataTempalte to UserControl in DataTemplate - c#

I want to share a ListViewItem from DataTemplate to a UserControl in DataTemplate using DataContext, i just spend two hours on this task, looked many sites, but dont find a requested answer, because everytime i want to get DataContext, it is null.
Short code, what i want to do:
In Page.xaml
<ListView Name="MainWindowLinesInfoListView1" IsItemClickEnabled="True" ItemClick="MainWindowLinesInfoListView1_ItemClick" Grid.Row="1" ItemsSource="{x:Bind pk1}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Przystanki">
<local:MainWindowLinesInfoFirst DataContext="{x:Bind self}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in UserControl called: MainWindowLinesInfoFirst
<Grid Margin="2" HorizontalAlignment="Center">
<TextBlock x:Name="MainWindowLinesInfoListView1TextBlock" Foreground="Navy" HorizontalAlignment="Center" TextWrapping="Wrap" />
</Grid>
and in .cs of this UserControl:
public MainWindowLinesInfoFirst()
{
this.InitializeComponent();
var a = this.DataContext as Przystanki;
}
and here is a simple class:
public class Przystanki
{
public Przystanki self { get { return this; } }
public string name { get; set; }
}
The problem is, that always when a this UserControl is called, a DataContext is not a "Przystanki" bot null.
Question is: How to send a DataContext to this UserControl?

What UWP does:
Creates MainWindowLinesInfoFirst control.
Sets its DataContext property to your required value.
Obviously, you can't read DataContext property in the constructor, because the control isn't created yet and there's no way for UWP to set a property before creating an instance.
What you want is to subscribe to the DataContextChanged event in the constructor. When UWP sets it, you'll be notified.

Related

In Xamarin.Forms, how to Bind to list of custom class, for ListView and print custom class properties?

In .xaml file I am trying to bind to a listed custom class as ObeservableCollection object.
I can successfully update my variables and get the ObservableCollection updated. I can check it rendering it as:
<ListView ItemSource="{Binding myCustomObservableCollection}"/>
However, even if I can determine the number of the entries in the list, I cannot access the properties of my custom class.
I tried with this, with no success as list's rows are empty. Even using Text="{Binding Id}" doesn't work since it tells me that "Id" is not a property inside myCustomViewModel:
<ListView
x:DataType="vm:CustomtViewModel"
BackgroundColor="LightSlateGray"
HasUnevenRows="True"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding myCustomObservableCollection}"
SeparatorColor="Black">
<ListView.ItemTemplate>
<DataTemplate>
<label Text="{Binding Source={StaticSource myCustomClass}", Path=Id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Of course I have inserted my custom class into the .xaml with:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
And Id is one of the properties I need into the public class in my Models
namespace myApp.Models {
public class myCustomClass : INotifyPropertyChanged
{
private string _id;
public event PropertyChangedEventHandler PropertyChanged;
public string Id
{
get => _id;
set {
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
}
}
So I wonder how to effectively read every entry of the list as an object which I could parse the properties in it.
Thanks so much
Did you check the official document about Binding Cells in the ListView? The myCustomClass didn't have to inherit from the INotifyPropertyChanged interface.
Just make sure there is public ObservableCollection<myCustomClass> { get; set; } in your viewmodel. Such as:
public class CustomtViewModel
{
public ObservableCollection<myCustomClass> myCustomObservableCollection { get; set; }
public CustomtViewModel()
{
// you can initialize the myCustomObservableCollection's data in the construction method.
}
}
In adddition, I see you used the x:DataType="vm:CustomtViewModel" for the listview. The official document said:
Set an x:DataType attribute on a VisualElement to the type of the object that the VisualElement and its children will bind to.
So you can just binding the Id like Jason said:
<ListView.ItemTemplate>
<DataTemplate>
<Label Text={Binding Id}/>
</DataTemplate>
</ListView.ItemTemplate>
In addition, you can refer to the official sample about listview mvvm binding on the github.This is the viewmodel's code and the page's code.
Also thanks to Liyun Zhang and ToolmakerSteve I came up with a solution.
Indeed it's important to set the correct x:DataType and I found out it can be done even multiple times pointing at different classes, linking different types of data
Here's my ListView in xaml now:
<ListView
x:Name="customListName"
x:DataType="vm:CustomViewModel"
ItemsSource="{Binding myCustomObservableCollection}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:myCustomClass"> <!--THIS SAVED THE DAY-->
<ViewCell>
<Label Text="{Binding Id}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now the object extracted from list is correctly read referencing to its own class.
The trick is about adding x:DataType="local:myCustomClass" to the DataTemplate tag after I added a reference in the xaml like this:
<ContentPage.Resources>
<local:myCustomClass x:Key="myCustomClass" />
</ContentPage.Resources>
(I insert this also here for ease of reading if someone else met the same issue)
It worked like a charm!
Hope this can save someone else from headache! Cheers.

WPF C# Binding Data from another Class

So I'm missing something simple or losing my mind. I am trying to reuse a class for multiple pages in a WPF application and bind the properties to the pages that instance it. I've tried setting the DataContext but I'm missing something. I'm loading the StockAnalysis page and then creating instance of the PriceChart class (this is the class for reuse) and I want the properties set in the PriceChart class to be the data to bind to the Stock.xaml.cs page. Even in setting the DataContext it is still looking for the StockAnalysis object. Why?
Stock.xaml.cs
public partial class StockAnalysis : Page
{
PriceChart PChart = new PriceChart();
public StockAnalysis()
{
InitializeComponent();
//Load The Data
List<Stock> HistoricalPrice = Database.GetPrices(ticker);
//Create The Charts
this.DataContext = PChart;
PChart.ShowPriceChart(HistoricalPrice);
}
}
Stock.xaml (Look at the Last TexBlock for the Binding of "LastPrice")
<Page x:Class="Stock.StockAnalysis"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:Stock"
mc:Ignorable="d"
d:DesignHeight="1000" d:DesignWidth="1200"
Title="Stock Analysis">
<StackPanel x:Name="LastClosePanel" Grid.Row="0" Grid.RowSpan="2" Grid.Column="5" Height="60" VerticalAlignment="Top" Margin="1,0,0,1" Style="{StaticResource LastCloseBackground}">
<TextBlock x:Name="LastCloseText" Foreground="OrangeRed" FontSize="12" HorizontalAlignment="Center" Margin="0,10,0,8">Last Close</TextBlock>
<TextBlock x:Name="LastCloseBind" Foreground="White" FontSize="16" HorizontalAlignment="Center" Text="{Binding LastPrice}"></TextBlock>
</StackPanel>
</Page>
PriceChart.cs (This is where I assign "LastPrice" in hopes to bind it to the TextBlock in stock.xaml.cs)
public class PriceChart
{
public string LastPrice { get; set; }
public void ShowPriceChart(List<Stock> FullList)
{
LastPrice = FullList[0].LastPrice.ToString("C");
//DO OTHER THINGS
}
}
The problem is that PriceChart doesn't implement any change notification. With the current code, this is how things will go when StockAnalysis gets created:
InitializeComponent() will create the TextBlocks and the binding. At this point, DataContext is null, so the binding will fail and the TextBlock stay empty.
this.DataContext = PChart will trigger a binding update (because DataContext is a DependencyProperty, which means it does support change notification). When the binding updates, it will pull the value of LastPrice, which is currently still empty.
ShowPriceChart will set the value of LastPrice, but because PriceChart doesn't support change notification, the binding doesn't know it needs to update, so the TextBlock stays empty.
To solve this, I would recomend your PriceChart implement the INotifyPropertyChanged interface per this article: How to: Implement Property Change Notification.
(Technically, moving PChart.ShowPriceChart(HistoricalPrice) before this.DataContext = PChart would also "solve" the problem, but only if you never need to update the bindings again after initialization.)

Bind a property, inside DataTemplateItem in ListBox, to an outside object in MainPage

I got ListBox with DataTemplate, inside DataTemplate I got another ListBox, trying to bind it's Visibility to another object which is found in the MainPage
XAML:
<ListBox x:Name="RegistersListView" ItemsSource="{x:Bind registersList}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="structures:Register">
<StackPanel>
<ListBox x:Name="FieldsListView" ItemsSource="{x:Bind fields_list}" Visibility="{x:Bind SomeVisibilityObjectIMain}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="structures:Field">
<Button Content="{x:Bind name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
public sealed partial class HWTab : Page
{
public ObservableCollection<Register> registersList = new ObservableCollection<Register>();
public var SomeVisibilityObjectIMain;
public HWTab()
{
InitializeComponent();
InitData();
this.DataContext = hwType;
}
....
}
I need to bind to "SomeVisibilityObjectIMain" somehow, I tried to bind with ElementName or even make object static, but could not succeed.
My bindable object is more complex than the example here but solve this will give me the way for solution.
You could use {Binding} instead of x:Bind. This way you could add a x:Name="Page" to your page and then use this name in the inner binding:
{Binding ElementName=Page, Path=MyProperty}
For {Binding} to work however, MyProperty must be actually a property. From your sample code (which uses var which is also invalid) it seems it is just a plain field, so you will need something like:
public string MyProperty {get;set;}
To also get PropertyChanged notifications, you will need to add a backing field and trigger PropertyChanged event.
However, overall a better solution would be to include all information a DataTemplate needs into the actual items which are bound to it. That means - you would create a custom view model type for the items, which would include the information that you need to control visibility.

How to use ElementName binding from resource?

I've added a MenuFlyout to a button in ItemsControl.ItemTemplate. Also I was able to bind current item as CommandParameter.
Now I want to bind Command to a MenuFlyoutItem.
In codebehind :
LayoutRoot.DataContext = this;
So if i bind to LayoutRoot I will actually bind to my current UserControl. But the following binding is not working:
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
It gives me not errors in Output but it's not working.
Here's the full example:
<controls:HeaderDecorator x:Uid="AccountsHeader" Text="Accounts" x:Name="LayoutRoot" Name="LayoutRoot">
<controls:HeaderDecorator.Resources>
<MenuFlyout x:Key="AccountMenuFlyout">
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding ActivateProfileCommand, ElementName=LayoutRoot}"
CommandParameter="{Binding}" />
</MenuFlyout>
</controls:HeaderDecorator.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Settings.Profiles}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding}" FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}" >
<i:Interaction.Behaviors>
<ic:ShowFlyoutBehavior />
</i:Interaction.Behaviors>
</HyperlinkButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</controls:HeaderDecorator>
Seems the problem is i'm trying to use shared object in Resources. Can I do it? And why not?
The issue you are seeing here is the MenuFlyoutItem is no longer in the datacontext you perhaps think it is. I'll try and explain this as best I can as a few I know who work with xaml have come across this and hit their heads off walls for days about it. It's also known to not show errors in your particular scenario; further increasing confusion.
In a nutshell. When the MenuFlyout is added inside the ItemTemplate of each item in your collection, it does not have access to the datacontext you perhaps think it does. In this case, the datacontext that the control now resides is actually the individual item within the collection it is sitting in.
There is however a solution to this. I have something similar to yourself. An ItemsControl which has it's ItemsTemplate defined that includes a UIElement who's FlyoutBase AP references a MenuFlyout defined in a resource dictionary.
The xaml is pretty much the same except I don't need the ElementName in the binding.
However, I have now turned my attention to the type that the collection holds. I have code that looks something like this.
public class AnItemToList
{
public AnItemToList(Action commandDel)
{
TestCommand = new RelayCommand(commandDel);
}
public string Name { get; set; }
public RelayCommand TestCommand { get; set; }
}
Note that the command is being defined in the item itself and that I'm passing the method that the command will execute via the constructor.
All I have to do for the command binding on the MenuFlyoutItem is
<MenuFlyoutItem Text="Activate"
Name="Activate"
Command="{Binding TestCommand}"/>
I don't have a command param set here as I just quickly put together a basic template Windows Phone app and the pre-packed ICommand implementation doesn't have a delegate set to take the param.
If you now stick a break point in the method the command is calling, you'll see it will be called from any of the MenuFlyoutItem's bound to the command that references it.
Bare in mind that this isn't the only way of solving this problem; but it is one I use myself on occasion. For example, in WPF XAML you can make use of RelativeSource to go looking for the command on a parent control's datacontext.
Hope this helps.
Here's a general "Pair" object:
public class Pair : DependencyObject
{
public static readonly DependencyProperty FirstProperty = DependencyProperty.Register("First",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public static readonly DependencyProperty SecondProperty = DependencyProperty.Register("Second",
typeof(object), typeof(Pair), new PropertyMetadata(null));
public object First
{
get { return GetValue(FirstProperty); }
set { SetValue(FirstProperty, value); }
}
public object Second
{
get { return GetValue(SecondProperty); }
set { SetValue(SecondProperty, value); }
}
}
In ItemTemplate i put something like this:
<DataTemplate>
<Grid>
<Grid.Resources>
<viewModel:Pair x:Key="Tuple" First="{Binding DataContext, ElementName=LayoutRoot}"
Second="{Binding}" />
</Grid.Resources>
<HyperlinkButton Content="{Binding Second.ProfileName}"
DataContext="{StaticResource Tuple}"
FlyoutBase.AttachedFlyout="{StaticResource AccountMenuFlyout}"
</HyperlinkButton>
</Grid>
</DataTemplate>
Now I can easily reference Tuple elements from my Resource like this:
<MenuFlyoutItem Text="Activate" Name="Activate"
Command="{Binding First.ActivateProfileCommand}"
CommandParameter="{Binding Second}" />

Why can't I Bind a viewmodel property to a dependency property of a custom control

I want to use a color picker in my wpf application and I saw a nice looking one on this codeproject page. The control works fine until I want to connect the control to a viewmodel.
I created a small test program with this viewmodel:
public class ColorViewModel : ViewModelBase
{
public ColorViewModel()
{
LineColor = Brushes.Yellow;
}
SolidColorBrush _brushColor;
public SolidColorBrush LineColor
{
get { return _brushColor; }
set
{
_brushColor = value;
RaisePropertyChanged(() => LineColor);
}
}
}
The test program has a textbox and the colorpicker controls:
<StackPanel Orientation="Horizontal">
<TextBlock Text="Please Select a Color" FontWeight="Bold" Margin="10"
Foreground="{Binding Path=LineColor, UpdateSourceTrigger=PropertyChanged}"/>
<vw:ColorPickerControlView x:Name="ForeColorPicker" Margin="10"
CurrentColor="{Binding Path=LineColor, UpdateSourceTrigger=PropertyChanged }"/>
</StackPanel>
In the loaded event of the main window in my test application I set the viewmodel to the datacontext like this:
DataContext = new ColorViewModel();
The problem is that I can't seem to bind the LineColor property of the viewmodel to the CurrentColor property of the ColorPickerControlView. The CurrentControl property of the ColorPickerControlView seems to be fine. The constructor looks like this:
public ColorPickerControlView()
{
this.DataContext = this;
InitializeComponent();
CommandBindings.Add(new CommandBinding(SelectColorCommand, SelectColorCommandExecute));
}
In the constructor of the UserControl there is the line this.DataContext = this; I read that is is necessary to bind the dependency properties. Do I override this line when I set my viewmodel to the datacontext and is that why I can't bind to the CurrentColor property? Is there any workaround? Or did I make another mistake?
You are right in thinking that the DataContext=this phrase in the UserControl's constructor preempts if from binding to an external viewmodel. It was disccussed in this question. This is easily remedied however. There is only one DependencyProperty in the UserControl's code behind that the xaml binds to: CurrentColor.
Do this:
Add a Name="Root" attribute to the UserControl tag of the
UserControl's xaml
Change the attribute (of the Border tag)
Background="{Binding
Path=CurrentColor}" to:
Background="{Binding
ElementName=Root,
Path=CurrentColor}"
Remove the offending DataContext=this
line from the UserControl's
constructor!
That should be all that there is to it. I wrote a proof of concept that demonstrates the above. If you like I can post it, but the code above should be all you need.
Both binding must be clashing the set the value of the property. Try Setting the Mode=OneWay
<StackPanel Orientation="Horizontal">
<TextBlock Text="Please Select a Color" FontWeight="Bold" Margin="10"
Foreground="{Binding Path=LineColor, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
<vw:ColorPickerControlView x:Name="ForeColorPicker" Margin="10"
CurrentColor="{Binding Path=LineColor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"/>
</StackPanel>
The line this.DataContext = this isn't really needed since you are replacing the DataContext with an instance of the ViewModel. You also do not need to assign the DataContext on the Loaded event handler. Just set it on the constructor. You can set it after the call to InitializeComponent method.
Remove the line DataContext = this in file ColorPickerControlView.xaml.cs
Change the Binding in ColorPickerControlView.xaml to Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type CustomWPFColorPicker:ColorPickerControlView}},
Path=CurrentColor}"

Categories