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.
Related
I'm trying to learn the MVVM structure. How can I update a variable that changes constantly in another class in the UI.
I created a simple example because the project codes are too much. But I failed.
I would be very grateful if you could tell me where I went wrong. Thanks.
MyModel
public class Temperature : INotifyPropertyChanged
{
private double _memsTemperature;
private double _cpuTemperature;
private double _animalTemperature;
public double MemsTemperature
{
get { return _memsTemperature; }
set
{
_memsTemperature = value;
OnPropertyChanged("MemsTemperature");
}
}
public double CpuTemperature
{
get { return _cpuTemperature; }
set
{
_cpuTemperature = value;
OnPropertyChanged("CpuTemperature");
}
}
public double AnimalTemperature
{
get { return _animalTemperature; }
set
{
_animalTemperature = value;
OnPropertyChanged("AnimalTemperature");
}
}
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
public Temperature()
{
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e)
{
MemsTemperature = MemsTemperature + 1;
CpuTemperature = CpuTemperature + 2;
AnimalTemperature = AnimalTemperature + 3;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
MainWindowViewModel
public class MainWindowViewModel
{
public double MemTemp { get; set; }
public MainWindowViewModel()
{
MemTemp = new Temperature().MemsTemperature;
}
}
Main Window Xaml and C# Code
<TextBlock Text="{Binding MemTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel should expose a Temperature property, e.g. like this:
public class MainWindowViewModel
{
public Temperature Temperature { get; } = new Temperature();
}
and the Binding should then look like this:
<TextBlock Text="{Binding Temperature.MemsTemperature}"/>
Neither Mode=TwoWay nor UpdateSourceTrigger=PropertyChanged makes sense on the Binding of a TextBlock's Text property.
The OnPropertyChanged method would simpler and safer be implemented like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You have a XAML page with UI controls that bind to those constantly-changing properties. When you send out the PropertyChanged notifications, the UI control will automatically update itself.
The problem with the code you wrote is that you never bound to the actual temperature. XAML doesn't know how to translate MemTemp into anything other than it's name unless you write a DataTemplate for it.
For example, (assuming a grid) something like this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Animal: "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MemTemp.AnimalTemperature}"/>
I would define an explicit worker class which performs the measurements. This class
has an event (OnMeasurement), which can be subscribed in the ViewModel:
// Arguments for the mesurement event (temperature, ...)
public class MeasurementEventArgs : EventArgs
{
public double Temperature { get; }
public MeasurementEventArgs(double temperature)
{
Temperature = temperature;
}
}
public class MeasurementWorker
{
private readonly CancellationTokenSource _tcs = new CancellationTokenSource();
// Provides an event we can subscribe in the view model.
public event Action<object, MeasurementEventArgs> OnMeasurement;
public void Stop()
{
_tcs.Cancel();
}
// Measurement routine. Perform a measurement every second.
public async Task Start()
{
try
{
var rnd = new Random();
while (!_tcs.IsCancellationRequested)
{
var temperature = 20 * rnd.NextDouble();
OnMeasurement?.Invoke(this, new MeasurementEventArgs(temperature));
await Task.Delay(1000, _tcs.Token);
}
}
catch (TaskCanceledException) { }
// TODO: Create an error event to catch exceptions from here.
catch { }
}
}
In your MainWindow class you instantiate your viewmodel and your worker:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new MeasurementWorker());
}
// Register in XAML with <Window ... Closing="StopMeasurement">
public async void StopMeasurement(object sender, System.ComponentModel.CancelEventArgs e)
{
var vm = DataContext as MainWindowViewModel;
await vm.StopMeasurement();
}
}
In your view model you can subscribe to the worker event and raise OnPropertyChanged in your callback function:
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _memsTemperature;
private readonly MeasurementWorker _mw;
private readonly Task _measurementWorkerTask;
public double MemsTemperature
{
get => _memsTemperature;
set
{
_memsTemperature = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MemsTemperature)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void ProcessMeasurement(object sender, MeasurementEventArgs args)
{
MemsTemperature = args.Temperature;
}
// You can call this if you want to stop your measurement. Should be called if you close your app.
public async Task StopMeasurement()
{
_mw.OnMeasurement -= ProcessMeasurement;
_mw.Stop();
// Clean shutdown
await _measurementWorkerTask;
}
public MainWindowViewModel(MeasurementWorker mw)
{
_mw = mw;
_mw.OnMeasurement += ProcessMeasurement;
_measurementWorkerTask = _mw.Start();
}
}
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");
}
textBox binding logContent in another class.
If i change value of variable logContent in MainWindow all working right. But if i change value of variable logContent in Test.cs (when i click on button) it is not working.
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
textBox.DataContext = Log.Instance;
Log.Instance.logContent += "aaa" + Environment.NewLine; //this working
}
private void button_Click(object sender, RoutedEventArgs e)
{
Test.Instance.Volaj();
}
}
Log.cs
class Log
{
public string logContent { get; set; }
private static Log instance;
public static Log Instance
{
get
{
if (instance == null)
{
instance = new Log();
}
return instance;
}
}
}
Test.cs
class Test
{
private static Test instance;
public static Test Instance
{
get
{
if (instance == null)
{
instance = new Test();
}
return instance;
}
}
public void Volaj()
{
Log.Instance.logContent += "bbb" + Environment.NewLine; //not working
}
}
XAML of textBox
<TextBox
x:Name="textBox"
HorizontalAlignment="Left"
Height="154"
Margin="10,155,0,0"
TextWrapping="Wrap"
Text="{Binding logContent}"
VerticalAlignment="Top"
Width="497"/>
in your code, the DataBinding is not working in any class.
You can see this with this simple test (you have to add the Loaded-Event in the MainWindow.xaml):
public MainWindow()
{
InitializeComponent();
textBox.DataContext = Log.Instance;
Debug.WriteLine($"Text [ctor before change]: {textBox.Text}");
// Output:
Log.Instance.logContent += "aaa" + Environment.NewLine; //this working
Debug.WriteLine($"Text [ctor after change]: {textBox.Text}");
// Output:
}
private void button_Click(object sender, RoutedEventArgs e)
{
Test.Instance.Volaj();
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Text [Loaded before change]: {textBox.Text}");
// Output: aaa
Log.Instance.logContent += "bbb" + Environment.NewLine;
Debug.WriteLine($"Text [Loaded after change]: {textBox.Text}");
// Output: aaa
}
The TextBox.Text Property does not change during the constructor. After the constructor is finished, the view reads the DataContext once and updates its Text property. That's why you see the text in your TextBox. When the Loaded-Event is fired, the TextBox already has the text-value. Other changes will not be displayed.
To make the binding work, you have to notify the view, that a property has changed. The INotifyPropertyChanged-Interface exists for this reason. So if you change your Log.cs to the following, it should work:
class Log : INotifyPropertyChanged
{
private string _logContent;
public string logContent
{
get { return _logContent; }
set { _logContent = value; OnPropertyChanged(); }
}
private static Log instance;
public static Log Instance
{
get
{
if (instance == null)
{
instance = new Log();
}
return instance;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
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.
I don't understand why my rectangles are not being shown.
I made the xaml, and data binded the canvas, and init properly.
What am I missing such that it only shows a blank screen.
It should show a digital figure 8.
MODEL:
namespace Final
{
class Model : INotifyPropertyChanged
{
// define our property chage event handler, part of data binding
public event PropertyChangedEventHandler PropertyChanged;
// implements method for data binding to any and all properties
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private double _topTopHorizontal;
public double topTopHorizontal
{
get { return _topTopHorizontal; }
set
{
_topTopHorizontal = value;
OnPropertyChanged("topTopHorizontal");
}
}
private double _leftTopHorizontal;
public double leftTopHorizontal
{
get { return _leftTopHorizontal; }
set
{
_leftTopHorizontal = value;
OnPropertyChanged("leftTopHorizontal");
}
}
public void initModel()
{
topTopHorizontal = 50;
leftTopHorizontal = 50;
}
}
}
Main
public partial class MainWindow : Window
{
private Model model;
public MainWindow()
{
InitializeComponent();
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// create an instance of our Model
model = new Model();
model.initModel();
}
}
}
You haven't set the DataContext for this window.
In constructor add:
public MainWindow()
{
InitializeComponent();
model = new Model();
DataContext = model;
}
Therefor, your window can access "leftTopHorizontal" and "topTopHorizontal".
And in your xaml change:
Canvas.Top ="{Binding topTopHorizontal}"
Canvas.Left="{Binding leftTopHorizontal}"
with:
Canvas.Top ="{Binding model.topTopHorizontal}"
Canvas.Left="{Binding model.leftTopHorizontal}"