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}"
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 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));
}
How do I bind FontSize for WPF TextBox in XAML to a class member variable?
I have a collection of fonts that I use through the application.
I would like to change the values of those fonts dynamically in my code behind and then have the changes reflected during runtime.
How do I achieve this?
Here is what my class definition looks like
public ClassFoo
{
public double FontSize {get; set;}
}
This is how I define my class in MainWindow.xaml.cs:
public ClassFoo SampleClass;
Here is my what my XAML looks like:
<TextBlock Name="txtSample" Text="SomeText"
FontSize="{Binding SampleClass.FontSize}"/>
Then at runtime, I instantiate the class and initialize it:
SampleClass = new ClassFoo()
{
FontSize = 16;
}
I would create it like that:
public class MainWindow : Page
{
public Foo Foo { get; set; }
public MainWindow()
{
DataContext = this;
}
}
public class Foo : INotifyPropertyChanged
{
private double _fontSize;
public double FontSize
{
get { return _fontSize; }
set
{
_fontSize = value;
OnPropertyChanged(nameof(FontSize));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and then call it like:
<TextBlock Name="txtSample" Text="SomeText"
FontSize="{Binding Foo.FontSize}"/>
Most likely you need a DataContext = this; in your constructor for Mainwindow.xaml.cs. You also need in Mainwindow.xaml.cs that returns SampleClass.
You can only bind to public properties so the first thing to do would be to make SampleClass a property:
public ClassFoo SampleClass { get; set; }
And if you intend to set it dynamically at runtime after the constructor of the window has returned, the window should implement the INotifyPropertyChanged interface and raise change notfications for the taget property to get automatically updated.
Finally the source of the binding must be set to the window somehow. You could set the Source property of the binding explicitly or set the DataContext of the TextBlock or any of its parent element to an instance of the window.
Try this implementation of the MainWindow class together with the XAML markup you posted:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
SampleClass = new ClassFoo()
{
FontSize = 16
};
}
private ClassFoo _sampleClass;
public ClassFoo SampleClass
{
get { return _sampleClass; }
set { _sampleClass = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
There must be a lot of questions surrounding this area but I couldn't find anything to help in my instance.
The problem I'm experiencing is getting my ViewModel, and specifically a property within ViewModel, to be updated to my View. Below is my implementation. I think I understand where I'm going wrong but not sure how to resolve it.
I have a Module that has a list and edit view. Quite simply lists domain objects and then ability to edit a domain object.
My xaml binds the DataContent to a ViewModel property in my View.
I then use the INavigationAware.NavigateTo method to navigate to my ViewModel and this is where I load the domain object.
The problem is that obviously this is not reflected back to the View. The view already has an instance of the ViewModel. This method worked fine when the ViewModel was using a list of objects using ObservableCollection. However, this did not work when using a simple object or even an ObservableObject.
Could someone please help my understanding or point me to some links with a better implementation of what I am trying to achieve?
MyModule
public class MyModule : IModule
{
private readonly IRegionManager _regionManager;
public MyModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyListView));
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyEditView));
}
}
XAML
<UserControl
DataContext="ViewModel">
...
<TextBlock Text="{Binding Path=MyDomainObject.AProperty}" />
...
View
public partial class MyEditView
{
public readonly static string ViewName = "MyEditView";
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
}
ViewModel
public class MyViewModel : INavigationAware
{
private readonly IRegionManager _regionManager;
public MyDomainObject MyDomainObject { get; set; }
public void Load(ViewModelKey key)
{
// get domain object
// this method worked when MyDomainObject was
// ObservableCollection<T> as just adding elements to list
// where this is creating a new instance of MyDomainObject
var id = parameter from navigationContext;
MyDomainObejct = server.GetDomainObject(id);
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
var key = key from navigationContext;
Load(key);
}
}
SOLUTION
public class MyEditViewModel : INavigationAware
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyDomainObject _myDomainObject;
public MyDomainObject MyDomainObject
{
get
{
return _myDomainObject;
}
set
{
if (value != _myDomainObject)
{
_myDomainObject = value;
NotifyPropertyChanged();
}
}
}
View
public partial class MyEditView
{
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!(sender is MyEditViewModel))
return;
ViewModel = (MyEditViewModel)sender;
}
}
For your binding to update you need to implement INotifyPropertyChanged and raise PropertyChanged Event on the set accessor of your domain object.
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public MyDomainObject MyDomainObject
{
get
{
return myDomainObject;
}
set
{
if(value != myDomainObject)
{
myDomainObject = value;
RaisePropertyChanged("MyDomainObject");
}
}
}
private void RaisePropertyChanged(String p)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Or as in the Prism book, inherit NotificationObject and call RaisePropertyChanged(()=> PropertyName) which is refactoring-safe
I'm new to Silverlight and i'm trying to use Databinding.
This looks simple but it's not working and I can't find why...
In my MainPage.xaml
<map:Map Name="bing_map" Height="578" Width="480"
ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}"
Center="{Binding Center, Mode=TwoWay}"
CredentialsProvider="{StaticResource BingMapsKey}" />
As you can see, I'm attempting a binding on ZoomLevel and Center.
In my MainPage.xaml.cs
The class inherit from INotifyPropertyChanged
In the constructor:
ZoomLevel = 12.0;
Center = new GeoCoordinate(0, 0);
The properties:
private double _zoom_level;
private double ZoomLevel
{
get { return _zoom_level; }
set {
if (_zoom_level == value) return;
_zoom_level = value;
RaisePropertyChanged("ZoomLevel");}
}
private GeoCoordinate _center;
private GeoCoordinate Center
{
get { return _center; }
set {
if (_center == value) return;
_center = value;
RaisePropertyChanged("Center"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
I'm I forgetting something?
I have stuck on this for 3 hours starting to be a while for a simple binding...
Thank you in advance for your help! :)
Try changing the properties to public:
private double _zoom_level;
public double ZoomLevel
{
get { return _zoom_level; }
set {
if (_zoom_level == value) return;
_zoom_level = value;
RaisePropertyChanged("ZoomLevel");}
}
private GeoCoordinate _center;
public GeoCoordinate Center
{
get { return _center; }
set {
if (_center == value) return;
_center = value;
RaisePropertyChanged("Center"); }
}
And also set the View DataContext: (as Ray mentioned in his answer)
public partial class MainPage
{
public MainPage()
{
this.DataContext = this;
}
}
It is highly recommended to use the MVVM pattern.
In addition to the properties needing to be public (as per MichaelS's answer), bindings reference the object that is set to the control's DataContext (or its parent's DataContext).
So typically you wouldn't have your Window implement INotifyPropertyChanged but you would create another class (normally called a ViewModel) that implements INotifyPropertyChanged and set that to the Window's DataContext.
e.g.
public class MainWindowViewModel : INotifyPropertyChanged
{
private GeoCoordinate _center;
public GeoCoordinate Center
{
get { return _center; }
set
{
if (_center == value) return;
_center = value;
RaisePropertyChanged("Center"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in your MainPage.xaml.cs you could do something like this
public partial class MainPage
{
public MainPage(MainWindowViewModel vm)
{
this.DataContext = vm;
}
}
Of course, a quick fix for you might be to just set your DataContext for the page to be itself.
e.g.
public partial class MainPage
{
public MainPage()
{
this.DataContext = this;
}
}