WPF binding to a global variable to update UI - c#

Let's say I have two windows and a trayicon context menu. Each of the windows has a togglebutton and the context menu has a checkable menu item. All three controls are designed to display and toggle the status of the same value.
How can I bind, in this case IsChecked, of the three controls to a single global variable that when one of the controls is checked/unchecked that the other controls will update accordingly? Should I just do an invoke or is there an MVVM solution? I'm new to WPF so I'm not sure the best/most correct way to accomplish this.

Lets say you have WindowA, WindowB, ..., WindowN and assume that they all are of different type.
Create a class, lets say CommonState, that encapsulates all common properties, commands, etc. and implements INotifyPropertyChanged
public class CommonState : INotifyPropertyChanged
{
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then declare an interface:
public interface ICommonStateWindow
{
CommonState { get; set; }
}
Make each window implement this interface:
public partial class WindowA : Window, ICommonState
{
public WindowA()
{
InitializeComponent();
}
// This property will be injected, do not re-assign
public CommonState CommonState { get; set; }
}
Inject the common state in each window prior to showing it, for example:
public partial class App : Application
{
private CommonState _state;
protected override void OnStartup(StartupEventArgs e)
{
_state = new CommonState() {IsChecked = true};
var wndA = new WindowA() { CommonState = _state };
var wndB = new WindowB() { CommonState = _state };
wndA.Show();
wndB.Show();
}
}
Remember to keep at least one reference to the created CommonState in some long living object (like App or the main window), so it does not get garbage collected at some point.
In the XAML you should bind using a RelativeSource, so that each new type of window you create can have its own independent ViewModel (DataContext):
<Window x:Class="Example.WindowA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowA" Height="300" Width="300">
<Grid>
<CheckBox IsChecked="{Binding CommonState.IsChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
The example, I've demonstrated is not the only way and I won't say "the best", but it solves the following problems:
Encapsulates the common (shared) state
Synchronizes the state between different instances (or types) of windows
Allows the CommonState to be extended independently of the window implementation (only the XAML needs to be updated)
Another possible solution is to register a singleton instance of the CommonState into a statically exposed inversion of control container (IoC) and make each concrete window's ViewModel obtain an instance to it. In this way you will avoid the injection step. This would be an overkill for small projects
I anyone is trying to run the above code, remember to remove StartupUri="MainWindow.xaml" from App.xaml

You can add to your codebehind bool IsChecked property and use it for all component you want. And you can change it components' event method to true or false.

Related

combobox in usercontrol not fill - WPF & mvvm

Good day, I'm new to WPF with MVVM, the problem in general is when filling a combox, but I'll give you some context:
I have a user control that contains different controls, including a combobox which I try to fill from the modelview but I am not successful. The user control is invoked from a main window in a few words the flow would be something like this
mainview.xaml->usercontrol.xaml->usercontrolmodelview.cs
this is where I define the source of the combobox that is inside the user control (usercontro.xaml):
<ComboBox x:Name="cbConcept" Width="150" ItemsSource="{Binding Path=Concepts}" DisplayMemberPath="textConcept" />
in this it is in usercontrolmodelview.cs linked to my user control, I define a list called Concepts that I fill it through a service (the service if it returns information and fills the list).
private IEnumerable<Concept> _concepts;
public IEnumerable<Concept> Concepts { get => _concepts; set { _concepts = value; } }
public usercontrolmodelview()
{
AddItemCommand = new ViewModelCommand(ExecuteAddCommand, ValidateAddCommand);
_api = new Api();
_memCache = new MemCache();
_ = LoadCatalogs();
}
private async Task LoadCatalogs()
{
_concepts = _memCache.GetCache<IEnumerable<Concept>>(KeysCache.CompanyCache);
if (_concepts == null)
{
_companys = await _api.GetConcept();
_memCache.SaveCache(_concepts, KeysCache.CompanyCache);
}
}
and this way I invoke the user control in my main window (xaml):
<ContentControl Content="{Binding currentChildView}"
Grid.Row="2"
Margin="20"/>
this the code in the main principal (cs):
public ICommand cmdControl { get; }
private ViewModelBase _curretnChildView;
public ViewModelBase currentChildView
{
get { return _curretnChildView; }
set
{
_curretnChildView = value;
OnPropertyChanged(nameof(currentChildView));
}
}
public MainViewModel()
{
cmdControl = new ViewModelCommand(ExecuteShowAddUserControl);
}
private void ExecuteShowAddUserControl(object obj)
{
currentChildView = new usercontrolmodelview();
}
xaml code where the command that shows the user control is linked:
<RadioButton Style="{StaticResource menuButton}"
Tag="{StaticResource colorClosed}"
Command="{Binding cmdControl}">
</RadioButton>
as extra data the user control if it is displayed in the main window.
I have tried to change the type of source by datacontext and even so the combobox is not filled
Your usercontrolmodelview should implement INotifyPropertyChanged to tell your view when a property changed and the view needs to refresh that proprety. This is espacially important when dealing with async operations.
A sample implementation could look like this:
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
I also like to create a ViewModelBase class which implements this interface and make all my viewmodels inherit from it, but this is personal preference.
In the Setter of your public IEnumerable Concepts, you call NotifyPropertyChanged(nameof(Concepts));
Please note, that if you collection of concepts might change later, you should use a ObservableCollaction and call the PropertyChangedEvent on the CollectionChanged Event handler.
Hope this helps
~BrainyXS

Label Content binding only updating once

My label only seems to get the data from the property it is bound to once. I have the Property raising the Property Changed event in the setter, but when the value of the property gets changed, it raises the event properly (I know this because of the break point I set), but the text in the Label on the window doesn't change. I should maybe also note that the window with the label isn't the main window, but a new one that pops up.
ViewModel:
public class PurchaseVerificationViewModel : ViewModelBase
{
private WindowService.WindowService windowService = new WindowService.WindowService();
private string _verificationQuestion = "Question"; //default so i can check if it changed in the window
public string VerificationQuestion
{
get { return _verificationQuestion; }
set
{
if (_verificationQuestion != value)
{
_verificationQuestion = value;
OnPropertyChanged(nameof(VerificationQuestion));
}
}
}
}
Window:
<Window>
<Window.DataContext>
<viewmodels:PurchaseVerificationViewModel/>
</Window.DataContext>
<Grid>
<Label Content="{Binding VerificationQuestion, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
There's no problem with your implementation of the INotifyPropertyChanged, since you are correctly invoking the notification that your property has been modified.
So if the problem is not with the one who's raising the notification, might it rather be with what is actively listening to it?
And the problem is that you're defining the DataContext of your Window to the class itself, rather than to the instance which you are utilizing and modifying in the code-behind of your application.
What is actually happening under the hoods, due to the way you defined your DataContext in xaml, is that a new PurchaseVerificationViewModel class is being constructed (is the not the one who are modifying on your logic) and therefore your VerificationQuestion will return it's default value (or rather the private backing field default value, "Question").
In reality the problem is that you have induced your listener to listen to the wrong thing.
Since you want the content of the Label (target) to be update based on a source change, what you have to do, is to set as the DataContextof the Window the specific instance which you are modifying on the logic of your application, and make sure you define it as a property!
public PurchaseVerificationViewModel myViewModel {get;set;}
For instance after InitializeComponent(), on your page constructor, you could initialize the property and set it as the DataContext, like this:
myViewModel = new PurchaseVerificationViewModel();
this.DataContext = myViewModel;

Access parent window's Grid from User Control

In my MainWindow VM i open the Views from my UserControls like this.
Usercontrol1 is the name of the View made in Xaml.
In my ViewModel of my MainWindow:
private static Grid _myMainGrid;
public static Grid MyMainGrid
{
get { return _myMainGrid; }
set { _myMainGrid = value; }
}
private void OpenUserControl(UserControl myUS)
{
if (MyMainGrid.Children.Count > 0)
{
MyMainGrid.Children.RemoveAt(0);
}
Grid.SetColumn(myUS, 1);
Grid.SetRow(myUS, 0);
MyMainGrid.Children.Add(myUS);
}
private void FindGrid(object obj)
{
var myGrd = obj as Grid;
if (myGrd != null)
{
MyMainGrid = myGrd;
}
}
The command binding to the Button executes this.
private void ExecuteCMD_OpenUserControl1(object obj)
{
FindGrid(obj);
Usercontrol1 _ucItem = new Usercontrol1();
OpenUserControl(_ucItem);
}
Now i want to open Usercontrol2 replacing Usercontrol1 in MyMainGrid from my MainWindow by pressing a button in Usercontrol1. So i have to access the parent Window.
Tried using this methode but can't get it to work in my case.
Let's say you have two children; it's trivial to generalize this to any number of children. Fortunately you've already got viewmodels and views, so we're most of the way there. It's just a matter of wiring it all together in a way that works well with WPF.
Here's a set of skeletal viewmodels. Main, and two children. MainViewModel creates its two child instances. ChildOneViewModel has a Next button command, bound to a Button in ChildOneView.xaml.
When the user clicks that button, we want to switch the active view to child two. Rather than have dependencies going in all directions, ChildOneViewModel is ignorant of what "next" really means; its parent, MainViewModel, is in charge of that. Everybody knows about his children; you've found that in programming, making a class know about its siblings or its parent creates problems.
So all ChildOneViewModel does is expose an event so MainViewModel knows when the button is clicked, and can take any action it likes when that happens. This is cool because what if we could be going to one of two different "next" pages, depending on what the user did in ChildOne? If we move that responsibility to the parent, everything becomes simpler. Easier to write, easier to reuse in a different context.
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ChildOne = new ChildOneViewModel();
ChildTwo = new ChildTwoViewModel();
ActiveChild = ChildOne;
ChildOne.NextButtonClicked += (s, e) => ActiveChild = ChildTwo;
}
#region ActiveChild Property
private INotifyPropertyChanged _activeChild = default(INotifyPropertyChanged);
public INotifyPropertyChanged ActiveChild
{
get { return _activeChild; }
set
{
if (value != _activeChild)
{
_activeChild = value;
OnPropertyChanged();
}
}
}
#endregion ActiveChild Property
public ChildOneViewModel ChildOne { get; private set; }
public ChildTwoViewModel ChildTwo { get; private set; }
}
#endregion MainViewModel Class
#region ChildOneViewModel Class
public class ChildOneViewModel : ViewModelBase
{
public event EventHandler NextButtonClicked;
// You already know how to implement a command, so I'll skip that.
protected void NextButtonCommandMethod()
{
NextButtonClicked?.Invoke(this, EventArgs.Empty);
}
}
#endregion ChildOneViewModel Class
#region ChildTwoViewModel Class
public class ChildTwoViewModel : ViewModelBase
{
}
#endregion ChildTwoViewModel Class
And here's the XAML that translates all of that into actual UI at runtime:
<Window.Resources>
<!--
These are "implicit datatemplates": They have no x:Key, so they'll be
automatically used to display any content of the specified types.
-->
<DataTemplate DataType="{x:Type local:ChildOneViewModel}">
<local:ChildOneView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ChildTwoViewModel}">
<local:ChildTwoView />
</DataTemplate>
</Window.Resources>
<Grid>
<!-- other stuff -->
<UserControl
Content="{Binding ActiveChild}"
/>
<!-- other stuff -->
You don't need OpenUserControl(), FindGrid(), any of that stuff.
I don't fully understand the structure of your application and there are most probably better ways of doing whatever you are trying to do, but you could get a reference to any open window in your application using the Application.Current.Windows property if that's your main issue:
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
Inside a loaded UserControl, you should also be able to get a reference to the parent window of this UserControl like this:
var mainWindow = Window.GetWindow(this) as MainWindow;

Open a new Window in MVVM

Lets say I have a MainWindow and a MainViewModel, I'm not using MVVM Light or Prism in this example.
In this MainWindow I want to click a MenuItem or Button to open a NewWindow.xaml not a UserControl.
I know how to use this with UserControl to open a new UserControl in my existing Window in a ContrntControl or a Frame.
<ContentControl Content="{Binding Path=DisplayUserControl,UpdateSourceTrigger=PropertyChanged}" />
Code
public ViewModelBase DisplayUserControl
{
get
{
if (displayUserControl == null)
{
displayUserControl = new ViewModels.UC1iewModel();
}
return displayUserControl;
}
set
{
if (displayUserControl == value)
{
return;
}
else
{
displayUserControl = value;
OnPropertyChanged("DisplayUserControl");
}
}
}
In the ResourceDitionary for MainWindow I have :
<DataTemplate DataType="{x:Type localViewModels:UC1ViewModel}">
<localViews:UC1 />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:UC2ViewModel}">
<localViews:UC2 />
</DataTemplate>
The thing is that I want to open a new Window, not a UserControl. So I use some code like this :
private ICommand openNewWindow;
public ICommand OpenNewWindow
{
get { return openNewWindow; }
}
public void DoOpenNewWindow()
{
View.NewWindowWindow validationWindow = new View.NewWindow();
NewWindowViewModel newWindowViewModel = new NewWindowViewModel();
newWindow.DataContext = ewWindowViewModel;
newWindow.Show();
}
and then a bind OpenNewWindow to a MenuItem or Button.
I know this is not the right way, but what is the right way to do this ?
Thanks!
There are two problems you need to solve with this type of application.
Firstly, you do not want to have the View-Model creating and displaying UI components directly. One of the motivations for using MVVM is to introduce test-ability in to your View-Model, and having this class pop up new windows makes this class harder to test.
The second problem you need to solve is how to resolve the dependencies in your application, or in this instance – how to you “hook up” the View-Model to the corresponding View? A maintainable solution to this latter problem is given by the use of a DI container. A very good reference to this subject is given by Mark Seemann’s Dependency Injection in .NET. He actually also discusses how to solve the first problem too!
To solve the former problem, you need to introduce a layer of indirection to your code, to make the View-Model not dependent on a concrete implementation of creating a new window. A very simple example is given in the code below:
public class ViewModel
{
private readonly IWindowFactory m_windowFactory;
private ICommand m_openNewWindow;
public ViewModel(IWindowFactory windowFactory)
{
m_windowFactory = windowFactory;
/**
* Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
* to the execution of the command.
* */
m_openNewWindow = null;
}
public void DoOpenNewWindow()
{
m_windowFactory.CreateNewWindow();
}
public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}
public interface IWindowFactory
{
void CreateNewWindow();
}
public class ProductionWindowFactory: IWindowFactory
{
#region Implementation of INewWindowFactory
public void CreateNewWindow()
{
NewWindow window = new NewWindow
{
DataContext = new NewWindowViewModel()
};
window.Show();
}
#endregion
}
Note that you take an implementation of IWindowFactory in the constructor of your View-Model, and it is to this object that the creation of the new window is delegated to. This allows you to substitute the production implementation for a different one during testing.

INotifyPropertyChanged binding not updating as expected

So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.
here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}
Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />

Categories