How to bind whole page to BindingContext? - c#

In my application I have a situation where I want to display some object on page and then change this object for different one.
So, let's consider I have MainPage.xaml.cs like this:
...
public Foo Item { get; set; }
public bool SomeCheck {
get {
return Item.Bar != "";
}
}
public MainPage() {
InitializeComponent();
SetItem();
BindingContext = this;
}
private void SetItem() {
Item = DifferentClass.GetNewItem();
}
private void Next_Clicked(object sender, EventArds e){
SetItem();
}
...
and MainPage.xaml like this:
...
<Label Text="{Binding Item.Bar}" IsVisible="{Binding SomeCheck}" />
<Button Text="Next" Clicked="Next_Clicked" />
...
So I want to bind whole page to BindingContext, to achieve this I've set BindingContext = this;. Behaviour which I want is to show Bar property of different objects returned by GetNewItem() and what I get is frozen page. In debugger Item is changing, but on page I have always value which I've set at the first call.
So the question is: can I somehow update BindingContext to show what I want? I tried calling OnPropertyChanged() but it doesn't work for me.
I know I can set up whole object like
BindingContext = { Bar = Item.Bar, SomeCheck = Item.Bar != "" };
and the it works, but of course my real scenario is more complex so I don't want to go this way.

Use OnPropertyChanged:
XAML:
<Label Text="IsVisible" IsVisible="{Binding MyIsVisible}" />
In the viewmodel, in your case in MainPage.xaml.cs:
private bool myIsVisible = true;
public bool MyIsVisible
{
get => myIsVisible;
set
{
myIsVisible = value;
OnPropertyChanged(nameof(MyIsVisible));
}
}

Related

ObservableCollection showing up in UI, but slow in Xamarin

There is a page where the user selects parameters to show the proper collection then on button click jumps to the next page (Coll) where it should show up.
User Selection Page XAML:
<ContentPage.BindingContext><xyz:UserSelectionViewModel</ContentPage.BindingContext>
...
<Button x:Name="Start" Command="{Binding LoadData}" Pressed="StartClick"/>
User Selection Page C#:
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (CollViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
...
vm.Subject = vm.Subject.ToLower();
}
UserSelectionViewModel:
public class UserSelectionViewModel : BaseViewModel
{
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
_pageService = DependencyService.Get<IPageService>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
FilteredData.AddRange(filtereddata);
OnPropertyChanged("FilteredData");
Debug.WriteLine(FilteredData.Count());
await Device.InvokeOnMainThreadAsync(() => _pageService.PushAsync(new Coll(FilteredData)));
}
}
Coll XAML:
<ContentPage.BindingContext><xyz:CollViewModel</ContentPage.BindingContext>
...
<CarouselView ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type z:Coll}}, Path=InheritedData}" ItemTemplate="{StaticResource CollTemplateSelector}">
...
Coll C#:
public partial class Coll : ContentPage
{
public ObservableRangeCollection<Feladatok> InheritedData { get; set; }
public Coll(ObservableRangeCollection<Feladatok> x)
{
InitializeComponent();
InheritedData = x;
OnPropertyChanged(nameof(InheritedData));
}
}
CollViewModel:
public class CollViewModel : UserSelectionViewModel { ... }
BaseViewModel:
private ObservableRangeCollection<Feladatok> inheriteddata;
public ObservableRangeCollection<Feladatok> InheritedData
{
get
{
return inheriteddata;
}
set
{
if (value != inheriteddata)
{
inheriteddata = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InheritedData"));
}
}
}
Managed to make it work like this with the help of Jason's tips. My only concern remaining is that: Won't this slow down the page that I load the observable collection two times basically? Is it a good practice as I have made it?
Eventually set the BindingContext to the VM and Binding from there. I still feel like it could be done more efficently or maybe that's how it is done. ViewModels are still new for me and I feel like it's much more code and slower with it. But I will close this, as it is working now.

Xamarin passing values using QueryProperty

I am a beginner at Xamarin and I am trying to pass value from one page to another using QueryProperty, but I keep getting null values.
Here is the Page where the value comes from:
<StackLayout>
<Button Text="Pass" Command="{Binding passCommand}"></Button>
</StackLayout>
The code behind:
public Page()
{
InitializeComponent();
passCommand = new Command(passFunc);
BindingContext = this;
}
public ICommand passCommand { get; }
private async void passFunc()
{
string str = "Hello";
await Shell.Current.GoToAsync($"{nameof(Page3)}?str={str}");
}
And here is the receiving page:
<StackLayout>
<Label Text="{Binding str}"/>
</StackLayout>
The code behind:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
showdisp();
}
public string str { set; get; }
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
The passed value should be put in the Label and the popup display alert. When I tried to put breakpoints, str value is still null. Navigating between pages are fine.
Can someone point out if where the error is T_T
Thanks in advance.
Your property "str" needs to raise a PropertyChanged event, so that the binding updates the value:
[QueryProperty(nameof(str), nameof(str))]
public partial class Page3 : ContentPage
{
public Page3()
{
InitializeComponent();
BindingContext = this;
// attention: this won't show the passed value,
// because QueryProperty values only are set after construction
//showdisp();
}
private string _str;
public string str
{
get => _str;
set
{
if(_str == value) return;
_str = value;
// Let the bound views know that something changed, so that they get updated
OnPropertyChanged();
// optional, call showdisp() when value changed
showdisp();
}
}
public async void showdisp()
{
await App.Current.MainPage.DisplayAlert("Hello", str, "OK");
}
}
However, since the parameter only is set after construction of Page3 finished, your showdisp() method won't have the correct value. You need to call it later.
You should also consider using a ViewModel and apply MVVM.

How do I bind a Picker async?

I'm loading via httpClient some values into a List. Now I want to bind this List to a Picker. But the Picker is empty.
I have a class "Trade" with different items, e.g. title.
The ViewModel (FirmsViewModel) has the following code:
public async Task GetTradesData()
{
var tradeList = await App.RestService.GetTradesAsync(true);
Trades = new ObservableCollection<Trade>(tradeList);
}
The "Trades" List is filled. Till this point it seems to be working.
In my Page.cs file I have the following code:
public FirmsPage()
{
InitializeComponent();
viewModel = new FirmsViewModel();
BindingContext = viewModel;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await viewModel.GetTradesData();
}
The XAML of the picker:
<Picker SelectedIndex="{Binding TradesSelectedIndex, Mode=TwoWay}"
ItemsSource="{Binding Trades}"
ItemDisplayBinding="{Binding title}"
Margin="0,15,0,0"
Title="Select a Trade">
</Picker>
If you are running the code, the Picker is always empty. Any suggestions?
Thanks.
That should be straight forward:
Make sure that you fire the PropertyChanged event after setting the Trades property
Make sure that this event is fired on the UI Thread
So if assuming your declaration of Trades looks like:
public ObservableCollection<Trade> Trades { get; private set; }
You could just call. RaisePropertyChanged("Trades"); (or whatever the equivalent is in your ViewModel type) right after assigning it in GetTradesData()
Alternatively you could change your declaration of your property:
private ObservableCollection<Trade> _trades;
public ObservableCollection<Trade> Trades
{
get => _trades;
set
{
_trades = value;
RaisePropertyChanged("Trades");
}
}
Or what I personally would prefer, is to simply initialize the ObservableCollection from the beginning and simply adding the items to it in GetTradesData():
public ObservableCollection<Trade> Trades { get; } = new ObservableCollection<Trade>();
and in GetTradesData():
foreach (var trade in tradeList)
Trades.Add(trade);

Set the SelectionChanged event of a ComboBox while binding its SelectedItem and ItemsSource in XAML

I'm trying to set up a ComboBox with its options binded from a list of strings, its default selected value binded from a setting, and with an event handler for its selection changed.
I want to configure it all using XAML like so:
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
But when I do that on startup it throws the error:
An unhandled exception of type
'System.Reflection.TargetInvocationException' occurred in
PresentationFramework.dll
If I only do some of it in XAML, then either set the SelectionChanged event or the ItemsSource programatically in C# like below it works fine. But I have a lot of these ComboBoxes so I would rather do it straight in the XAML.
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}" />
With this C#:
public IEnumerable<string> Routes
{
get { return LubricationDatabase.GetRoutes(); }
}
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set { } /* side question: without this, it throws a parse exception. Any idea why? */
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
RoutesComboBox.SelectionChanged += RouteFilter_SelectionChanged;
}
I've also tried the solution found here:
private string _defaultRoute;
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set
{
if (_defaultRoute != value)
{
_defaultRoute = value;
// this fires before `SelectedValue` has been
// updated, and the handler function uses that,
// so I manually set it here.
RoutesComboBox.SelectedValue = value;
SelectionChangedHandler();
}
}
}
Which is okay, but is pretty bulky and probably more work than is worth it when I can just programatically assign the SelectionChanged event.
Again if possible I'd like to do it all using XAML because I have a lot of these ComboBoxes and initializing them all like this in the C# will look awful.
Any ideas?
Why are you binding with SelectedItem when you're not going to update the item when a user changes their selection? Not sure what your event handler is doing, but I have a working solution just the way you wanted it.
In short, you need to keep track of the DefaultRoute using a backing field. Also, you need to notify the UI when the selected item changes in your view model; which by the way is something you don't seem to be doing, MVVM. You should only be hooking into the selection changed event if you plan on updating the view in some way. All other changes should be handled in your view models DefaultRoute setter
XAML
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
Code
public partial class MainWindow : Window, INotifyPropertyChanged
{
public IEnumerable<string> Routes
{
get
{
return new string[] { "a", "b", "c", "d" };
}
}
public string DefaultRoute
{
get
{
return _defaultRoute;
}
set
{
_defaultRoute = value;
// Handle saving/storing setting here, when selection has changed
//MySettings.Default.DefaultRoute = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
DefaultRoute = MySettings.Default.DefaultRoute;
}
private string _defaultRoute;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void RouteFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public static class MySettings
{
public static class Default
{
public static string DefaultRoute = "a";
}
}

WPF Databinding, no clue why it isn't working

I have successfully bound window items to view models before using wpf data binding, almost, the exact same way as I'm doing here.
I have a GUI with the XAML for my TextBlock binding to change the colour and text with the system state;
<TextBlock
HorizontalAlignment="Left" Margin="200,359,0,0" TextWrapping="Wrap"
Text="{Binding Path=StateText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="565" Height="84"
Background="{Binding Path=StateColour, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
I set the datacontext to my view model in my xaml.cs;
MobilityWelcomeViewModel mobilityWelcomeViewModel = new mobilityWelcomeViewModel();
public MobilityWelcome()
{
InitializeComponent();
this.DataContext = this.mobilityWelcomeViewModel;
}
I have this constructor which writes to my data model via the specified adapter;
public class MobilityWelcomeViewModel
{
private bool State;
private string _Text;
private Brush _StateColour;
BackgroundWorker StateWorker = new BackgroundWorker();
}
public ShellEMobilityWelcomeViewModel()
{
this._ANMStateColour = Brushes.White;
this.ANMStateWorker.DoWork += this.ANMStateWorker_DoWork;
this.ANMStateWorker.RunWorkerCompleted += this.ANMStateWorker_RunWorkerCompleted;
this.ANMStateWorker.RunWorkerAsync();
this._ANMText = "Loading ANM State";
IApplicationPointAdapter testWrite = AdapterFactory.Instance.GetApplicationPointAdapter();
testWrite.WriteBinary("HMI.EV.SITE1.STATUS.CONTACTBREAKEROPEN", false);
}
In my view model I have the properties;
public Brush StateColour
{
get { return this._StateColour; }
set { this._StateColour = value; }
}
public string StateText
{
get { return this._Text; }
set { }
}
I have background workers which I can see change these values in debug.
I'm really stumped here. The whole binding thing seems pretty simple at surface so, from my fairly new and probably naive, knowledge of it I can't see what I've done wrong.
Thanks in advance. (also i've changed the variable names to disguise my project so if there is a spelling disparoty between like objects or likewise just ignore it)
I think you are setting the datacontext but not initialising your ViewModel in the right place.
Just to double check you can use tools like Snoop to see what is going wrong.
You should be initialising your ViewModel in the contructor
like below.
public MobilityWelcome()
{
InitializeComponent();
mobilityWelcomeViewModel = new mobilityWelcomeViewModel();
this.DataContext = this.mobilityWelcomeViewModel;
}
Also make sure you are implementing INotificationPropertyChanged.
Your property setters should be like below
public Brush StateColour
{
get { return this._StateColour; }
set { this._StateColour = value;
OnPropertyChanged("StateColour");
}
}

Categories