XAML binding to a codebehind class property collection is blank (WPF) - c#

My codebehind defines a simple class with properties and a constructor as so:
public class Question
{
public string[] Answers
{
get; set;
}
public int CorrectAnswerIndex
{
get; set;
}
public Question(string[] answers, int correctIndex)
{
this.Answers = answers;
this.CorrectAnswerIndex = correctIndex;
}
}
There then exists a public object of that type that gets initialised in the window's constructor like so:
CurrentQuestion = new Question(
new string[] { "First", "Second", "Third", "Fourth" }, 2
);
I then have the following XAML in an attempt to print out all of the possible answers from said question.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The local namespace is defined previously as the CLR namespace.
However, my list emerges entirely empty. There are no binding errors at runtime.
What's going on here? It seems a simple example that just won't run. I feel I've missed something "obvious."

This will look at ListBox.DataContext for a property named Answers, and try to use that for the ItemsSource.
<ListBox ItemsSource="{Binding Answers}">
ListBox.DataContext will be inherited from the parent Grid. Unfortunately, the grid's DataContext is a string, and strings don't have a property called Answers. So the Binding can't do anything and gives you null.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
XAML implicit conversions are a Do-What-I-Mean thing, thus a source of much confusion. There are times when you can put local:CurrentQuestion in an attribute value and have it be taken as a data type -- but this is not one of those times. And a data type isn't what you meant to provide anyway. You wanted a property by that name. But local: is a namespace, a literal CLR namespace like System.Windows.Controls, not a reference to an object.
Here's how the XAML in a UserControl can bind to a property of the UserControl. If it's a Window, change UserControl to Window.
<Grid Margin="150,150,150,150">
<ListBox
ItemsSource="{Binding CurrentQuestion.Answers, RelativeSource={RelativeSource AncestorType=UserControl}}">
I'm just guessing that CurrentQuestion is a property of the UserControl. Let me know if it's somewhere else.
You're also probably going to run into problems when you update CurrentQuestion, unless it's a dependency property. If it's a plain old CLR property like this, the UI won't be notified when its value changes:
public Question CurrentQuestion { get; set; }

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.

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.

Creating a ViewModel for each item of ItemsControl

I have defined a type Board which containes a public property
ObservableCollection<Column> Columns
I would like to display it with use of MVVM pattern.
I created BoardView and bound it to BoardViewModel. BoardViewModel exposes public property Board of type Board.
BoardView contains a control ItemsControl which sets ItemsSource={Binding Board.Columns}.
<ItemsControl ItemsSource="{Binding Board.Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
BoardColumnView should show properties of Column type and this works good.
My problem is that I want to create a ViewModel for BoardColumn, and instead of showing only properties of Column type I want to show BoardColumnViewModel which would have defined inside a Column property.
How can I achieve that?
thanks in advance!
You could simply define a Columns property in your BoardViewModel that would contain a collection of BoardColumnViewModel. Something like this:
public ObservableCollection<BoardColumnViewModel> Columns { get; private set; }
You will need to initialize this property somewhere in BoardViewModel, for example:
public BoardViewModel(...)
{
...
Columns = new ObservableCollection<BoardColumnViewModel>(Board.Columns.Select(c => new BoardColumnViewModel(c)));
}
And then bind to that property, instead of Board.Columns:
<ItemsControl ItemsSource="{Binding Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As a general principle in MVVM is that it is not recommended to bind directly to a model. Instead you should always try to bind to a view model. This is why you have the problem you've described - because you bind to a public property Board, which is your model.
In my understanding is BoardColumnView an own UserControl?
If so just set the DataContext of this UserControl with this.DataContext = new BoardColumnViewModel(); in code behind or use XAML equivalent. Then create in your BoardViewModel an ObserveableCollection<> which holds multiple Instances of BoardColumnView and set the ItemSource to this collection. In your BoardColumnView XAML define your Layout, Bindings, etc. which are displayed in BoardView.
So every time you add a BoardColumnView to the ItemsSource, which is bound to the collection, a new instance of BoardColumnViewModel is getting created.
...That is my understanding of your Problem, but I might be completely wrong :).

WPF Creating different ListBox row templates based on a bound value

I do not have any code at the moment to share. Just a design question.
I have a class that defines a label, and an associated entry type, which I would like to bind to a ListBox. If the type is for example, "Postal Code", I need the ListBox to create the row as a TextBlock and a TextBox. For "Yes/no", I need it to know instead to create a TextBlock with a CheckBox beside it. There will likely be 7 or 8 of these different row types.
What is the best way to approach this?
Have a look at the ItemTemplateSelector Property. This property allows you to provide custom logic for choosing which template to use for each item in a collection.
First define your various templates in a resource dictionary...
<Application>
<Application.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<!-- template here -->
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<!-- template here -->
</DataTemplate>
</Application.Resources>
</Application>
Then, create a custom DataTemplateSelector...
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var myObj= item as MyObject;
if (myObj != null)
{
if (myObj.MyType is PostalCode)
{
return Application.Resources["TextBoxTemplate"] as DataTemplate;
}
if (myObj.MyType is YesNo)
{
return Application.Resources["CheckBoxTemplate"] as DataTemplate;
}
}
return null;
}
}
Then, its just a matter of using the ItemTemplateSelector property...
<Window>
<Window.Resources>
<local:MyTemplateSelector x:Key="tempSelector" />
</Window.Resources>
<ListBox ItemSource="{Binding items}" ItemTemplateSelector="{StaticResource tempSelector}" />
</Window>
You can use the DataTrigger class.
A DataTrigger allows you to set property values when the property value of the data object matches a specified Value.
Alternatively, you can use the DataTemplateSelector class.
Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object.
The best way to approach this would be to have a collection property containing all of the items that you want to see in your ListBox, bind that collection to a control that displays lists of items, and use different data templates to change the visuals used for each type of item.
For example, you might have a postal code type:
public class PostalCodeEntry
{
public string Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
And a "Boolean" type:
public class BooleanEntry
{
public bool Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
You said you wanted a label for each entry type, so a base class would be a good idea:
public abstract class EntryBase
{
public string Label { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
Then BooleanEntry and PostalCodeEntry would derive from EntryBase.
That's the Models sorted. You just need a collection of these so that you can bind to them from the UI. Add an appropriate collection property to your Window or ViewModel:
public ObservableCollection<EntryBase> Entries { get; private set; }
In your UI (the View, implemented in XAML), use an instance of a control that knows how to bind to a list of items and visualize them. In WPF this would be an ItemsControl or a control that derives from it such as ListBox or ListView:
<ItemsControl ItemsSource="{Binding Entries}" />
You can see how we bind the ItemsSource property to our code-behind property named Entries. The ItemsControl (and its descendants) knows how to convert those items into a visual representation. By default, your custom object (EntryBase in our example) will be converted into a string and displayed as a text block. However, by using data templates you can control how the conversion from object to visual happens.
If you add a couple of data templates to the resources section like so:
<Window ... xmlns:my="clr-namespace:---your namespace here---">
<Window.Resources>
<DataTemplate DataType="{x:Type my:PostalCodeEntry}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type my:BooleanEntry}">
<CheckBox Content="{Binding Label}" IsChecked="{Binding Value}" />
</DataTemplate>
</Window.Resources>
Then add the <ItemsControl ... element after that, then you should see a TextBlock/TextBox combo for PostalCodeEntry types and a CheckBox for BooleanEntry types.
Hopefully if you can get this working it will give you an idea how you could extend it to cope with other model types and their matching data templates.

How to bind a static property in a different class to a GridViewColumn of WPF ListView?

Firstly I am using this for the ListView control itself:
ItemsSource="{Binding AllEffects}"
where 3 GridViewColumns already binded to to AllEffects.
But I have 2 more GridViewColumns that I want to bind to a separate static property found in:
public static class AllSupport
{
public static EffectSupportLookup<HardwareType, List<EffectSupport>>
}
public class EffectSupport
{
public bool IsSupported {get;set;}
}
I have tried this:
<GridViewColumn
Width="Auto"
Header="GPU">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Margin="0"
HorizontalAlignment="Center"
IsChecked="{Binding AllSupport, Mode=TwoWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
But at runtime, it complains that the there is no property called AllSupport on AllEffects. I don't want to store it inside AllEffects because this is a separate class already compatible with the UI, so I just want to bind it to:
AllSupport.EffectSupport[GPU].IsSupported
Any ideas?
Use x:Static Markup Extension.
Something like (never tested):
<Window xmlns:local="AssemblyName">
<ItemsControl
ItemsSource="{Binding Source={x:Static local:AllSupport.EffectSupport}}" />
</Window>
Or thru to a CollectionViewSource.
Update:
You have to specify internal path
Are you sure the local xmlns points to the right clr namespace (get assistance from the VS Xaml Intellisense)
Does your app compile before setting it in xaml?
Be more specific with your generic class implmenetation, what are you trying to achieve? is it a dictionary? a generic class? please reedit your code to give us the right look of your scenario.
I have 2 more GridViewColumns that I want to bind to a separate static property
I don't think you can do that, 1 ItemsSource means 1 SourceCollection.
But you can easily use LINQ to create a ad-hoc collection that includes those 2 columns
I don't want to store it inside AllEffects because this is a separate class already compatible with the UI,
If this means it's a ViewModel class, and if you want those columns in the View, then that is a very strong reason they should be stored in that class. Or in a separate, derived, ViewModel.

Categories