How to override ViewModel on calling a page with Shell - c#

I have a call
await Shell.Current.GoToAsync($"{viewPath}");
and also defined all the pages with routes and all the viewModels:
services.AddSingleton<MainPage>();
services.AddSingleton<ItemPage>();
services.AddSingleton<ItemsPage>();
services.AddSingleton<MainPageViewModel>();
services.AddSingleton<ItemPageViewModel>();
services.AddSingleton<ItemsPageViewModel>();
services.AddSingleton<ItemsPageModifiedViewModel>();
my idea is to call i.e ItemsPage with ItemsPageViewModel which is default, and in some scenarios i want to call ItemsPage with ItemsPageModifiedViewModel .
Is it possible to override the BindingContext?

You could use a static viewmodel for the same model.
I have a shell with tabs of Page1, Page2. And Page 2 has a button to do the navigation.
Page1ViewModel:
public class Page1ViewModel : INotifyPropertyChanged
{
public string _str;
public event PropertyChangedEventHandler PropertyChanged;
public string str
{
get
{
return _str;
}
set
{
_str = value;
OnPropertyChanged("str");
}
}
public Page1ViewModel()
{
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Page1.xaml:
<Label Text="{Binding str}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
Page1 code behind:
public static Page1ViewModel viewmodel { get; set; }
public Page1()
{
InitializeComponent();
viewmodel = new Page1ViewModel();
viewmodel.str = "hello";
this.BindingContext = viewmodel;
}
Page2 code behind:
private async void Button_Clicked(object sender, EventArgs e)
{
Page1.viewmodel.str = "test";
await Shell.Current.GoToAsync("//Page1");
}

Related

WPF C# PropertyChanged always null

I have played around with this for a while and decided to see if someone can help, I have set in the constructor of StatusInfo the DataContext = this and didn't work. When I write a string to ScreenStatusBarText it does call the OnPropertyChanged method but every time the PropertyChanged value is null. I The status block I have at the bottom of the screen. I have a tab section above this stack panel that has many components that use bindings and work.
Screen Code
<StackPanel Margin="0,1047,0,0">
<Grid Name="StatusBarItemGrid">
<TextBlock Name="StatusBarText" Text="may the force be with you" VerticalAlignment="Center" HorizontalAlignment="Stretch" />
</Grid>
</StackPanel>
Data Model:
public partial class StatusInfo : INotifyPropertyChanged
{
private string screenStatusBarText;
public StatusInfo()
{
BindScreenStatusBarText();
screenStatusBarText = "Initialized";
}
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged("StatusBarText");
}
}
private void BindScreenStatusBarText()
{
Binding b = new Binding();
b.Source = screenStatusBarText;
b.Mode = BindingMode.OneWay;
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
b.Path = new PropertyPath("StatusBarText");
MainWindow.mainWindow.StatusBarText.SetBinding(TextBlock.TextProperty, b);
MainWindow.mainWindow.StatusBarText.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
My main :
public partial class MainWindow : Window
{
public static StatusInfo status;
public MainWindow()
{
InitializeComponent();
SourceInitialized += MainWindow_SourceInitialized;
}
private void MainWindow_SourceInitialized(object sender, EventArgs e)
{
SetUpDisplay();
}
private void SetUpDisplay()
{
status = new StatusInfo();
}
}
Set the Binding in XAML instead of code behind:
<TextBlock Text="{Binding ScreenStatusBarText}" />
And use a view model like
public class StatusInfo : INotifyPropertyChanged
{
private string screenStatusBarText = "Initialized";
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged(nameof(ScreenStatusBarText));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propertyName));
}
}
with an instance of the view model class assigned to the MainWindow's DataContext:
private readonly StatusInfo statusInfo = new StatusInfo();
public MainWindow()
{
InitializeComponent();
DataContext = statusInfo;
}
You may now access the view model class at any time later, e.g. in an event handler of an element of MainWindow:
statusInfo.ScreenStatusBarText = "Something";
I think your going to struggle doing your binding in code behind.
Having said that, with regards to why your PropertyChanged value is null. You've simply made a typo, as-is you're notifying subscribers that a property that doesn't exist has changed. One solution to avoid such typos is to use nameof.
public string ScreenStatusBarText
{
get { return screenStatusBarText; }
set
{
screenStatusBarText = value;
OnPropertyChanged(nameof(ScreenStatusBarText));
}
}
It occurred to me you may also have meant that your event was null. This simply means you don't have any subscribers. See Why is my "Event" always null?.
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) // I have a subscriber.
handler(this, new PropertyChangedEventArgs(propertyName));
}

UWP Boolean not being updated in UI if set after awaited async call

I have a public boolean in my UWP app used to show/hide a ProgressBar. I've used the same pattern elsewhere and it seems to work okay, but I'm having an issue on a specific page where it doesn't seem to update in the UI only if I set the boolean after an awaited async call (same pattern as the working page).
Here is my XAML View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Vm.IsLoaded }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
And the codebehind:
public sealed partial class MainPage : Page
{
public MainPageViewModel Vm => DataContext as MainPageViewModel;
public MainPage()
{
this.InitializeComponent();
this.DataContext = new MainPageViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Vm.GetProjectData();
}
}
Here is my MainPageViewModel.cs
public class MainPageViewModel : ViewModel
{
public ObservableCollection<Project> Projects { get; set; } = new ObservableCollection<Project>();
private bool _isLoaded;
public bool IsLoaded
{
get { return _isLoaded; }
set
{
_isLoaded = value;
OnPropertyChanged();
}
}
public async Task GetProjectData()
{
// If I put `IsLoaded = true;` here it displays `true` in the UI
var projectsResponse = await HttpUtil.GetAsync(StringUtil.ProjectsUrl());
// If I put `IsLoaded = true;` here it still displays `false` in the UI
if (projectsResponse.IsSuccessStatusCode)
{
var projectsResponseString = await projectsResponse.Content.ReadAsStringAsync();
var projects = JsonUtil.SerializeJsonToObject<List<Project>>(projectsResponseString);
foreach (var project in projects)
{
Projects.Add(project);
}
IsLoaded = true;
}
}
}
And my ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
No matter where I put the IsLoaded = true; it always hits OnPropertyChanged().
Here is my working code:
ProjectViewViewModel.cs:
public class ProjectViewViewModel : ViewModel
{
public ObservableCollection<Story> MyData { get; set; } = new ObservableCollection<Story>();
private bool _dataIsLoaded;
public bool DataIsLoaded
{
get { return _dataIsLoaded; }
set
{
_dataIsLoaded = value;
OnPropertyChanged();
}
}
public async Task GetData(Project project)
{
DataIsLoaded = false;
var stringResponse = await HttpUtil.GetAsync(StringUtil.Query(project.Id, "MB"));
if (stringResponse.IsSuccessStatusCode)
{
// Do Stuff
DataIsLoaded = true;
}
}
}
ProjectView.xaml.cs
public sealed partial class ProjectView : Page
{
public Project Project { get; set; }
public bool IsLoaded { get; set; }
public ProjectViewViewModel Vm => DataContext as ProjectViewViewModel;
public ProjectView()
{
this.InitializeComponent();
this.DataContext = new ProjectViewViewModel();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
Project = e.Parameter as Project;
Vm.GetData(Project);
}
}
I feel like I'm missing something extremely obvious but I can't see the wood through the trees and it's driving me nuts. Any help is greatly appreciated!
I believe the problem you are having is in your mark up. x:Bind has a default binding mode of OneTime; so the text in your text block is bound to the value of IsLoaded at application start up, or when the data context for the text block changed.
Setting the binding mode to OneWay should result in the value in the text block updating after the async function has returned.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{ x:Bind Path=Vm.IsLoaded, Mode=OneWay }" Margin="112,272,-112,-272"></TextBlock>
</Grid>
If you're interested, this article goes into detail on the use of x:Bind. Also, this article covers the values in the BindingMode enumeration.

How to bind textbox object to ViewModel

Trying to make my first application with the simple logging function to the TextBox on main form.
To implement logging, I need to get the TextBox object into the logger's class.
Prob - can't do that :) currently have no error, but as I understand the text value of TextBox is binding to my ViewModel, because getting 'null reference' exception trying to execute.
Logger.cs
public class Logger : TextWriter
{
TextBox textBox = ViewModel.LogBox;
public override void Write(char value)
{
base.Write(value);
textBox.Dispatcher.BeginInvoke(new Action(() =>
{
textBox.AppendText(value.ToString());
}));
}
public override Encoding Encoding
{
get { return System.Text.Encoding.UTF8; }
}
}
ViewModel.cs
public class ViewModel
{
public int ThreadCount { get; set; }
public int ProxyTimeout { get; set; }
public static TextBox LogBox { get; set; }
//private TextBox _LogBox;
//public TextBox LogBox {
// get { return _LogBox; }
// set {
// _LogBox = value;
// }
//}
}
launching on btn click, MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Logger logger = new Logger();
logger.Write("ewgewgweg");
}
}
MainWindow.xaml
<Window
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"
xmlns:local="clr-namespace:tools"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" x:Class="tools.MainWindow"
mc:Ignorable="d"
Title="Tools" Height="399.387" Width="575.46">
<TextBox x:Name="logBox"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto" HorizontalAlignment="Left" Height="137" Margin="10,222,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="394" Text="{Binding Path = LogBox, Mode=TwoWay}"/>
You have several issues in your code:
Don't bring controls (TextBox) in your viewmodel, if you do there's no use in trying to do MVVM.
The Text property in XAML has to be of the type String or something that can be converted to a string. You're binding a control, which will result in showing System.Windows.Controls.TextBox (result of .ToString()) on your screen instead of actual text.
Your LogBox property should implement INotifyPropertyChanged
You don't want TwoWay binding, as the text flows from your logger to the UI, you don't need it to flow back. You might even consider using a TextBlock instead or make the control readonly so people can't change the content.
You don't want static properties or static viewmodels, read up on dependency injection on how to pass dependencies.
You will be flooding your UI thread by appending your characters one by one. Consider using another implementation (but I won't go deeper into this for this answer).
Keeping all above in mind, I transformed your code to this.
MainWindow.xaml
<TextBox x:Name="logBox"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="137" Margin="10,222,0,0"
TextWrapping="Wrap" Width="394" Text="{Binding Path = LogBox}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private Logger _logger;
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
DataContext = viewModel;
_logger = new Logger(viewModel); // passing ViewModel through Dependency Injection
}
private void button1_Click(object sender, RoutedEventArgs e)
{
_logger.Write("ewgewgweg");
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public int ThreadCount { get; set; }
public int ProxyTimeout { get; set; }
private string _logBox;
public string LogBox
{
get { return _logBox; }
set
{
_logBox = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Logger.cs
public class Logger : TextWriter
{
private readonly ViewModel _viewModel;
public Logger(ViewModel viewModel)
{
_viewModel = viewModel;
}
public override void Write(char value)
{
base.Write(value);
_viewModel.LogBox += value;
}
public override Encoding Encoding
{
get { return System.Text.Encoding.UTF8; }
}
}
You can use string instead of TextBox as follow as
In view model class
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _logBox;
public string LogBox
{
get {return _logBox;}
set
{
if(value != _logBox)
{
_logBox=value;
OnPropertyChanged("LogBox");
}
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
and in writer method you just
public void writer (string str)
{
ViewModel.LogBox = str;
}
You can define ViewModel as static or create new object from ViewModel and access the object in logger class as you want!
hope this helped.

WPF - Show command-line args in a label

I have a wpf application that needs to be called with several command line arguments. How do I show them in the labels that I have put in the window just for that reason?
I tried to implement data binding, but without success, - the variable is read and assigned correctly, but for some absurd reason is not shown on screen, in the label I want.
Here is the code:
public partial class MainWindow : Window
{
public Notification _notif = new Notification();
public MainWindow()
{
InitializeComponent();
this.DataContext = new Notification();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
App.Current.Shutdown();
}
}
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e){
if (e.Args.Length >= 4)
{
MainWindow mainWindow = new MainWindow();
Label count_label = (Label)mainWindow.FindName("count");
count_label.DataContext = mainWindow._notif;
System.Diagnostics.Debug.WriteLine(mainWindow._notif.count + " - notif.count");
// bind the Date to the UI
count_label.SetBinding(Label.ContentProperty, new Binding("count")
{
Source = mainWindow._notif,
Mode = BindingMode.TwoWay
});
//assigning values to the labels
System.Diagnostics.Debug.WriteLine(count_label.Content + " - content of the label 'count'");
mainWindow._notif.count = e.Args[0];
System.Diagnostics.Debug.WriteLine(e.Args[0] + " is the argument n. 0");
System.Diagnostics.Debug.WriteLine(mainWindow._notif.count + " - notif.count");
System.Diagnostics.Debug.WriteLine(count_label.Content + "-------------------");
System.Diagnostics.Debug.WriteLine(count_label.Content + " - content of the label 'count'");
mainWindow._notif.count = "1234";
System.Diagnostics.Debug.WriteLine(mainWindow._notif.count + " - notif.count");
System.Diagnostics.Debug.WriteLine(count_label.Content + " - content of the label 'count'");
}
}
}
public class Notification : INotifyPropertyChanged
{
private string _count;
public string count {
get {
return _count;
}
set {
_count = value;
OnPropertyChanged("count");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
And here you can see a snippet from the xaml:
<Label x:Name="count" Content="{Binding count}" HorizontalAlignment="Center" Margin="0,10,486,0" VerticalAlignment="Top" RenderTransformOrigin="-2.895,-0.769" Height="80" Width="145" FontFamily="Arial" FontSize="64" HorizontalContentAlignment="Center"/>
Thank you anticipately.
The example illustrate how to display arguments in a label.
This is the entry point of the application:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
var argumentsInfo = BuildArgumentsInfo(e.Args);
var viewModel = new MainWindowViewModel(argumentsInfo);
var window = new MainWindow(viewModel);
window.Show();
}
private string BuildArgumentsInfo(string[] args)
{
return args.Any()
? args.Aggregate((arg1, arg2) => arg1 + " " + arg2)
: "No arguments";
}
}
This is the view model (a data context of the view):
public interface IMainWindowViewModel
{
string Arguments { get; set; }
}
public class MainWindowViewModel : IMainWindowViewModel, INotifyPropertyChanged
{
private string _arguments;
public MainWindowViewModel(string argumentsInfo)
{
Arguments = argumentsInfo;
}
public string Arguments
{
get { return _arguments; }
set
{
_arguments = value;
RaisePropertyChanged("Arguments");
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This is the view (code behind):
public partial class MainWindow : Window
{
public MainWindow(IMainWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
And this is a label in the view (XAML):
<Label Content ="{Binding Arguments}"></Label>
Important! Your must delete StartupUri="MainWindow.xaml from App.xaml file because MainWindow is launched from a code behind.

How to data-bind a label

How to bind the content of a label to the class2 property PropName?
Class2 is not directly being used in Mainwindlow.xmal.cs.
Class1 is being used in Mainwindow.xmal.cs
And Class2 is being used in Class1.
Here is the code I'm using:
class Class2:INotifyPropertyChanged
{
string _PropName;
public string PropName
{
get
{
return this._PropName;
}
set
{
this._PropName = value;
OnPropertyChanged("PropName");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainWindow : Window,INotifyPropertyChanged
{
Class1 class1ob;
public MainWindow()
{
InitializeComponent();
class1ob = new Class1();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
class1ob.changeProp();
}
}
I want to bind the content of a label eith the Class2 property - PropName.
How can I do that?
Try this.
XAML
....
<Label Name="label" Content="{Binding Path=PropName}"/>
....
On your WindowLoad set DataContext for Label.
label.DataContext = class1ob.class2ob;//instance of class

Categories

Resources