So, I have a statusbar as UserControl.
Model:
public class StatusBarModel : BindableBase
{
private string _status;
public string Status
{
get { return _status; }
set
{
_status = value;
RaisePropertyChanged("Status");
}
}
private int _p_value;
public int P_Value
{
get { return _p_value; }
set
{
_p_value = value;
RaisePropertyChanged("P_Value");
}
}
}
ViewModel:
public class StatusBarVM : BindableBase
{
readonly source.elements.StatusBar.StatusBarModel _model = new source.elements.StatusBar.StatusBarModel();
public StatusBarVM()
{
_model.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
}
public string Status
{
get { return _model.Status; }
set { _model.Status = value; }
}
public int P_Value
{
get { return _model.P_Value; }
set { _model.P_Value = value; }
}
}
And for example I wanna change Status variable from others ViewModels.
How I can do it? I have seen examples with only buttons and etc.
There are multiple ways to achieve your requirement. as #bitclicker says, you can use static class that hold its value. But I think It is too much that makes it static class, because that variable value may be used only two viewmodel.
I suggest you communicate between two view model. you will find Prism's event aggregator or you could implement your own event publish-subscriber model. making your own event pub-sub model would help you to make a first step into the design pattern.
You could create a static class to hold that value.
public static class Globals()
{
public static StatusBarModel GlobalStatus { get; set; }
}
Then whenever you want to alter it you just do
Globals.GlobalStatus.Status = "something";
Globals.GlobalStatus.P_Value = 14;
does that accomplish what you need?
Related
I have SettingsViewModel with:
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public SettingsViewModel()
{
}
private string _gaugeColor;
public string GaugeColor
{
get => Preferences.Get("GaugeColor", "#17805d");
set
{
Preferences.Set("GaugeColor", value);
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
I set color by HEX string.
Then in PanelViewModel I have:
private Color _gaugeColor;
public Color GaugeColor
{
get => Color.FromHex(Preferences.Get("GaugeColor", "#17805d"));
set
{
_gaugeColor = value;
OnPropertyChanged();
}
}
Now if I change HEX string from Settings view in UI, color does not change in PanelViewModel until I restart an application.
Question is: How to make color change in PanelViewModel right after it has been changed in SettingsViewModel?
I have tried to add this into PanelViewModel, but apparently this creates a new instance of SettingsViewModel and Color does not follow into PanelViewMode. Maybe there is some direct solution and I am using Xamarin.Essentials wrong?
public PanelViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private Color _gaugeColor;
public Color GaugeColor
{
get => Color.FromHex(Preferences.Get("GaugeColor", "#17805d"));
set
{
_gaugeColor = value;
OnPropertyChanged();
}
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
this.GaugeColor = Color.FromHex(e.Settings.GaugeColor);
}
private SettingsViewModel SettingsViewModel { get; }
https://learn.microsoft.com/en-us/dotnet/api/xamarin.essentials.preferences?view=xamarin-essentials
Question is: How to make color change in PanelViewModel right after it
has been changed in SettingsViewModel?
Yes, a simple method is to use MessagingCenter.
The MessagingCenter class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between them.
You can refer to the following code:
In SettingsViewModel.cs, we can publish a message in the constuctor of it,just as follows:
public class SettingsViewModel
{
public string Title { get; set; }
public SettingsViewModel() {
Title = "SettingsView";
MessagingCenter.Send<Object, Color>(this, "Hi", Color.Yellow);
}
}
And in PanelViewModel.cs ,we can subscribe to this message:
public class PanelViewModel
{
public string Title { get; set; }
public PanelViewModel() {
Title = "PanelView";
MessagingCenter.Subscribe<Object, Color>(this, "Hi", async (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("-----> receive color= " + arg);
// here ,we can use the received color to update the UI
});
}
}
Note:
Once we receive the color, we can use the received color to update the UI, and the color field in the PanelViewModel.cs should implement interface INotifyPropertyChanged.
This is the XAML of the radio. Nothing else is editing this. Once this is set it is not changing. But somehow no matter what it is setting the XML to "false".
Here is how I save the XML file (works just fine).
There are 3 radio buttons, as you can see, that I am trying to get set to false or true but they all just get saved as false.
<RadioButton x:Name="sx80" Content="Cisco SX80" HorizontalAlignment="Left" Margin="701,244,0,0" VerticalAlignment="Top" GroupName="codecType" TabIndex="17" FontWeight="Normal" Height="25" Width="95" Padding="0,2"/>
class SaveXml
{
public static void savedata(object obj, string filename)
{
XmlSerializer sr = new XmlSerializer(obj.GetType());
TextWriter writer = new StreamWriter(filename);
sr.Serialize(writer, obj);
writer.Close();
}
}
Here is the main class that tells it what information we are saving to the XML file.
public class information
{
private string city;
private string chairCount;
private string stateSelect;
private string HostNameIPTyped;
private string VTCmac;
private string vtcUser;
private string vtcPass;
private string VTCserial;
private string AssetTag;
private string SIPURI;
private string SystemName;
private string firstName;
private string lastName;
private string contactPhone;
private string provisionerName;
private string provisionerInitials;
private string provisionDate;
private bool sx80;
private bool codecPlus;
private bool codecPro;
public string postcity
{
get { return city; }
set { city = value; }
}
public string postchairCount
{
get { return chairCount; }
set { chairCount = value; }
}
public string poststateSelect
{
get { return stateSelect; }
set { stateSelect = value; }
}
public string postHostNameIPTyped
{
get { return HostNameIPTyped; }
set { HostNameIPTyped = value; }
}
public string postVTCmac
{
get { return VTCmac; }
set { VTCmac = value; }
}
public string postvtcUser
{
get { return vtcUser; }
set { vtcUser = value; }
}
public string postvtcPass
{
get { return vtcPass; }
set { vtcPass = value; }
}
{ e164 = value; }
}
public string postVTCserial
{
get { return VTCserial; }
set { VTCserial = value; }
}
public string postAssetTag
{
get { return AssetTag; }
set { AssetTag = value; }
}
public string postSIPURI
{
get { return SIPURI; }
set { SIPURI = value; }
}
public string postSystemName
{
get { return SystemName; }
set { SystemName = value; }
}
public string postfirstName
{
get { return firstName; }
set { firstName = value; }
}
public string postlastName
{
get { return lastName; }
set { lastName = value; }
}
public string postcontactPhone
{
get { return contactPhone; }
set { contactPhone = value; }
}
public string postprovisionerName
{
get { return provisionerName; }
set { provisionerName = value; }
}
public string postprovisionerInitials
{
get { return provisionerInitials; }
set { provisionerInitials = value; }
}
public string postprovisionDate
{
get { return provisionDate; }
set { provisionDate = value; }
}
public bool postsx80
{
get { return sx80; }
set { sx80 = value; }
}
public bool postcodecPlus
{
get { return codecPlus; }
set { codecPlus = value; }
}
public bool postcodecPro
{
get { return codecPro; }
set { codecPro = value; }
}
}
The code you posted doesn't show any data binding on the RadioButton or how you've set your DataContext. But you said in the comments that the strings are working so I assume you've set the DataContext somewhere. If you can update your question to show how your Window/View is bound to the information object it will be easier to give you a more accurate solution. You also said the following in one of your comments:
Yes, it is actually being saved as false. If it didn't find a value it would just show nothing. :-) <postsx80>false</postsx80>
The default value for a bool is actually false, so even if no value is retrieved from your RadioButton, your XML file will still show false.
Your RadioButton's would normally be bound like this, depending on how your DataContext is set. Notice the Binding in the IsChecked property. The Mode=TwoWay means that the UI can set the value of the property and not just read it:
<RadioButton x:Name="sx80" Content="Cisco SX80" IsChecked="{Binding Info.postsx80, Mode=TwoWay}" />
In the code behind of this Window I have created a public property called Info which contains an instance of your information class. The RadioButton above is bound the the postsx80 property of this information instance so you would need to pass this instance to your savedata method like below.
public partial class MainWindow : Window
{
public information Info { get; set; } = new information(); // The UI is bound to this instance
public MainWindow()
{
InitializeComponent();
this.DataContext = this; // I've set the Window's DataContext to itself
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SaveXml.savedata(Info, "somefile.xml");
}
}
You should also implement INotifyPropertyChanged which will notify the UI when a property's value has changed. For example your information class could look like this:
// You will need to add the following namespaces
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace YourAppsNamespace
{
public class information : INotifyPropertyChanged // Implement the INotifyPropertyChanged interface
{
private bool sx80;
public bool postsx80
{
get { return sx80; }
set {
sx80 = value;
OnPropertyChanged(); // Notify the UI that this property's value has changed
}
}
// This code raises the event to notify the UI which property has changed
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
You would need to add OnPropertyChanged() to the setters of all of your properties.
You also mentioned in the comments that you don't know how to use auto properties. An auto property is basically a shorter way to write a property when there are no additional actions which need to be performed when getting or setting a value. For example, this:
private bool someBool;
public bool SomeBool
{
get { return someBool; }
set { someBool = value; }
}
Would just become:
public bool SomeBool { get; set; }
There is no need to create the private variable or define the body of the getter and setter. This is handled automatically for you. This is only suitable if you don't need to perform any additional actions in the getter or setter. So in my example above where we need to call OnPropertyNotifyChanged() in the setter, you wouldn't be able to use an auto property.
An additional tip is that you can simply type prop in Visual Studio and press Tab twice to insert an auto property without having to type it out yourself. You then simply change the data type, press Tab again to move to the name and change that. The same can be done for a full property like the ones you wrote by typing propfull.
I would like to have a control that allows a property to be shown if another property's value is set to a specific value. The following is a much simplified example of what I would like:
public class CustomButton : Control
{
private ButtonType _bType = ButtonType.OnOff;
private Int32 _minPress = 50; // 50 mS
public ButtonType Button_Type
{
get { return _bType; }
set { _bType = value; }
}
public Int32 Minimum_Press_Time // Only for momentary buttons
{
get { return _minPress; }
set { _minPress = value; }
}
}
public enum ButtonType
{
Momentary,
OnOff
}
On adding CustomButton to a Windows.Forms form, the Minimum_Press_Time will only show in the Properties window if Button_Type is changed to ButtonType.Momentary.
Is such a thing possible?
Yes, its possible to get close but it looks a little strange. I've done this on some controls before. Here is a full example of what you would need to do:
public partial class CustomButton : Control
{
private ButtonType _buttonType = ButtonType.OnOff;
private CustomButtonOptions _options = new OnOffButtonOptions();
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
public ButtonType ButtonType
{
get { return _buttonType; }
set
{
switch (value)
{
case DynamicPropertiesTest.ButtonType.Momentary:
_options = new MomentaryButtonOptions();
break;
default:
_options = new OnOffButtonOptions();
break;
}
_buttonType = value;
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public CustomButtonOptions ButtonOptions
{
get { return _options; }
set { _options = value; }
}
public CustomButton()
{
InitializeComponent();
}
}
public enum ButtonType
{
Momentary,
OnOff
}
public abstract class CustomButtonOptions
{
}
public class MomentaryButtonOptions : CustomButtonOptions
{
public int Minimum_Press_Time { get; set; }
public override string ToString()
{
return Minimum_Press_Time.ToString();
}
}
public class OnOffButtonOptions : CustomButtonOptions
{
public override string ToString()
{
return "No Options";
}
}
So basically what is happening is you are using an ExpandableObjectConverter to convert an abstract type to a set of options. You then use the RefreshProperties attribute to tell the property grid that it will need to refresh the properties after this property changes.
This is the easiest way I've found to come as close to what you are asking for as possible. The property grid doesn't always refresh the right way so sometimes there will be a "+" sign next to an options set with no expandable properties. Use the "ToString" in the properties to make the display on the property grid look intelligent.
I'm newbie in MVVM design pattern, and I have these viewmodels :
ClassAViewModel
public class ClassAViewModel : INotifyPropertyChanged
{
private int _nbre = 0;
public int Nbre
{
get
{
return _nbre;
}
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
And ClassBViewModel
PUBLIC class ClassBViewModel: INotifyPropertyChanged
{
private Boolean _IsBiggerthanFive = false;
public bool IsBiggerthanFive
{
get
{
return _IsBiggerthanFive;
}
set
{
_IsBiggerthanFive = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsBiggerthanFive"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I need to know if a mecanism of notification between two viewmodels exists , ie in my case if _nbre > 5 in the first viewmodel, the second viewmodel will be notified and the value of _IsBiggerthanFive will be changed. So:
How can two viewmodels communicate between them without instanciate one in the other ?
What is the best way to accomplish this task?
I agree with other commenters that the mediator/pub-sub/event aggregator/messenger is a good way to go. If you're not using an MVVM framework with a built-in solution, then I recommend this simple approach that takes advantage of the Reactive extensions:
public class EventPublisher : IEventPublisher
{
private readonly ConcurrentDictionary<Type, object> subjects
= new ConcurrentDictionary<Type, object>();
public IObservable<TEvent> GetEvent<TEvent>()
{
var subject =
(ISubject<TEvent>) subjects.GetOrAdd(typeof (TEvent),
t => new Subject<TEvent>());
return subject.AsObservable();
}
public void Publish<TEvent>(TEvent sampleEvent)
{
object subject;
if (subjects.TryGetValue(typeof(TEvent), out subject))
{
((ISubject<TEvent>)subject)
.OnNext(sampleEvent);
}
}
}
That's your whole event aggregator. Pass an instance of it into each view model, and store it as a reference. Then create a class to store your event details, let's say "ValueChangedEvent":
public class ValueChangedEvent
{
public int Value
{
get { return _value; }
}
private readonly int _value;
public ValueChangedEvent(int value)
{
_value = value;
}
}
Publish like this from the first view model:
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
_eventPublisher.Publish(new ValueChangedEvent(value));
}
Subscribe in the other class using GetEvent:
public class ClassBViewModel: INotifyPropertyChanged, IDisposable
{
private readonly IDisposable _subscriber;
public ClassBViewModel(IEventPublisher eventPublisher)
{
_subscriber = eventPublisher.Subscribe<ValueChangedEvent>(next =>
{
IsBiggerthanFive = next.Value > 5;
});
}
public void Dispose()
{
_subscriber.Dispose();
}
}
A messenger service is a solution. MVVM Light Toolkit has an implementation of this. What you can do with it, is listen to a specific type of message in your viewmodel and handle it through the messenger. http://www.mvvmlight.net/
I have made a Base Form which is inherited by most Forms in the application. Base form contains a Status Bar Control that displays user name which is internally a static string. User can Switch User at any point in the application by pressing a button on status bar. At this point the user name in the status bar should also change, as if now it only changes in code and UI has no idea about the change. I have googled around and found that i need to bind the label with that static string by implementing a INotifyProperty Interface. I have implemented many example code without success.
Appreciate any help
use BindableAttribute for the property you want to bind a control to it.
[Bindable(true)]
public int Username {
get {
// Insert code here.
return 0;
}
set {
// Insert code here.
}
}
You must implement a class to notify prop changed and therefore the prop can not be static. Combine with a singleton pattern and you have yout solution.
public class Global : INotifyPropertyChanged
{
private string _userName;
public string UserName
{
get
{
return this._userName;
}
set
{
if (this._userName == value)
{
return;
}
this._userName = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("UserName"));
}
{
}
public event PropertyChangedEventHandler PropertyChanged;
private Global() {}
public static readonly Global Get = new Global();
}
Usage:
var currUserName = Global.Get.UserName;
Global.Get.PropertyChanged += (s, e) => Console.WriteLine(e.PropertyName);
Global.Get.UserName = "John";
And bind to Global.Get to property UserName.
I would:
1- Add a timer to the base form to update the status bar. (the timer resolution is uo to your requirement).
the timer Tick handler would be something like this:
private void timerStatusUpdate_Tick(object sender, EventArgs e)
{
toolStripStatusLabelMessage.Text = StatusMessage();
}
2 - Add a virtual StatusMessage method to your base class:
class BaseForm : Form
{
.......
public virtual string StatusMessage()
{
return "override me!";
}
}
3- override StatusMessage in all your derived classes
class XXXForm : BaseForm
{
........
public override string StatusMessage()
{
return "XXXForm status message";
}
}
I use Reactive Extensions for these things
For example if you have a Context class with a property UserName
you could do this
public static class Context
{
public static Subject<string> UserChanged = new Subject<string>();
private static string user;
public static string User
{
get { return user; }
set
{
if (user != value)
{
user = value;
UserChanged.OnNext(user);
}
}
}
}
And then on your forms just do
Context.UserChanged.ObserveOn(SynchronizationContext.Current)
.Subscribe(user => label.Text = user);
The ObserveOn(SynchronizationContext.Current) makes it safe for cross thread operation calls