I noticed that it happends not only in the one project but on multiple too so I will provide simple example. I've got such xaml:
<Page
x:Class="TestApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Content="Button" Command="{Binding PressedButton}" HorizontalAlignment="Left" Margin="0,-10,0,-9" VerticalAlignment="Top" Height="659" Width="400"/>
</Grid>
</Page>
my classes to binding data:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
public class Command : ICommand
{
private Action<object> action;
public Command(Action<object> action)
{
this.action = action;
}
public bool CanExecute(object parameter)
{
if (action != null)
{
return true;
}
else
{
return false;
}
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (action != null)
{
action((string)parameter);
}
}
}
public class TestViewModel : ObservableObject
{
public ICommand PressedButton
{
get
{
return new Command((param) => { });
}
}
}
and main page:
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
DataContext = new TestViewModel();
}
It's weird but PressedButton runs only on application start(isn't that weird it runs on start?). After that, even after button click there is nothing triggered. I can't figure out what's wrong.
I think you may be causing binding issues by returning a new command each time the "getter" is called. Try setting the command once, in your constructor (for example).
public MainPage()
{
PressedAdd = new Command(param => SaveNote());
}
public ICommand PressedAdd { get; private set; }
In the SaveNote() method, you could test the values and either save (or not save) them:
private void SaveNote()
{
if (NoteTitle == null || NoteContent == null)
return;
// Do something with NoteTitle and NoteContent
}
Related
I am totally a new to the WPF application and start learning how to code the application.
During I tried to write a simple code to examine how it works, the code could not work as expected.
The intention is just to get the current system time by Datetime.Now and show it on Textblock updated every 500ms. - I just want test the environment that the code is running and processing behind the window to get/calculate the latest data and show it on the window.
However, with the simple code below, the text on the window just get the time once and never been updated while the loop is running and "ClockText" is being updated.
I am sure I made a stupid mistake, but I could not find where the wrong point is. Please give me your help!
[MainWindow.xaml]
<Window x:Class="wpfTest.MainWindow"
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:wpfTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Center" FontSize="40"
Text="{Binding ClockText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
[MainWindow.xaml.cs]
public partial class MainWindow : Window
{
private Clock clk;
public MainWindow()
{
InitializeComponent();
clk = new Clock() { ClockText = "HH:MM:SS" };
this.DataContext = clk;
clk.ClockLogic(ref clk);
}
}
public class ClockViewModel : INotifyPropertyChanged
{
private string clockText;
public string ClockText
{
get { return clockText; }
set
{
clockText = value;
NotifyPropertyChanged("ClockText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged == null)
{
return;
}
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public class Clock
{
public string ClockText { get; set; }
public async void ClockLogic()
{
while (true)
{
ClockText = DateTime.Now.ToString();
Console.WriteLine(ClockText); // for debug
await Task.Delay(500);
}
}
}
}
Since you bind to the ClockText property of the Clock and not to the ClockViewModel, the Clock class needs to implement INotifyPropertyChanged and raise the PropertyChanged event for the ClockText property whenever you set it:
public class Clock : INotifyPropertyChanged
{
private string clockText;
public string ClockText
{
get { return clockText; }
set
{
clockText = value;
NotifyPropertyChanged(nameof(ClockText));
}
}
public async void ClockLogic()
{
while (true)
{
ClockText = DateTime.Now.ToString();
Console.WriteLine(ClockText); // for debug
await Task.Delay(500);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
You don't seem to initialize and use your ClockViewModel class somewhere.
I'm new to C# and UWP. After many days of reading official microsoft docks and stackoverflow I still cannot understand what am I doing wrong.
Here is extremely simple test app.
I am trying to change grid's background on click. When application starts the grid's background is bg-1.jpg as expected. After click the value of property PicturePath have changed, but Grid's background have not.
What am I missing?
Here is a simple xaml page
<Page
x:Class="Test.Views.StartPage"
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">
<Grid PointerPressed="Grid_PointerPressed">
<Grid.Background>
<ImageBrush ImageSource="{Binding PicturePath, Mode=OneWay}" />
</Grid.Background>
</Grid>
</Page>
Code behind
public sealed partial class StartPage : Page
{
public StartPage()
{
this.InitializeComponent();
DataContext = new {
TestBackgroundClass.getInstance().PicturePath,
};
}
private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
TestBackgroundClass.getInstance().PicturePath = "ms-appx:///Assets/bg-2.jpg";
}
}
and a singleton class
public class TestBackgroundClass : INotifyPropertyChanged
{
private static TestBackgroundClass instance;
private string _picturePath { get; set; }
public string PicturePath
{
get
{
return _picturePath;
}
set
{
_picturePath = value;
NotifyPropertyChanged();
}
}
public TestBackgroundClass()
{
_picturePath = "ms-appx:///Assets/bg-1.jpg"
}
public static TestBackgroundClass getInstance()
{
if (instance == null) instance = new TestBackgroundClass();
return instance;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I am very new to WPF and relatively new to C# programming (programming in general), and I'm trying to develop a WPF application.
I have tried to go through several posts similar to this, but I can't seem to find the answer of why this is not working.
So, I'm having a hard time understanding the MVVM architecture, how and what it requires to switch between multiple user controls binded to a single <ContentControl />.
From what I understand and read so far, is that I have to bind the view model like this:
<ContentControl Content="{Binding ApplicationViewModel}"/>
So here is what I want to a achieve:
An ApplicationWindow.xaml with sidebar menu on the left side that will be shown at all times when the application is running, and a <ContentControl/> on the remaining space. Buttons shown on the sidebar menu will be:
Main (will show MainView.xaml User Control, should be the default User Control)
Settings (will show SettingsView.xaml User Control)
Exit (will close the application)
I understand that I need to bind the buttons to ICommand commands, and I understand the concept of a RelayCommand.cs class.
So let's jump into the simplified code of my idea and figure out what I need to understand and what I may have misunderstood in the process.
What MainView.xaml and SettingsView.xaml contain are not important right now, as I'm just trying to figure out how to show them in my application.
Here's the ApplicationWindow.xaml:
<Window x:Class="WpfApp1.ApplicationWindow"
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:WpfApp1"
xmlns:v="clr-namespace:WpfApp1.View"
xmlns:vm="clr-namespace:WpfApp1.ViewModel"
mc:Ignorable="d"
Title="ApplicationWindow" Height="1080" Width="1920"
WindowStyle="None" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<v:MainView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<v:SettingsView/>
</DataTemplate>
</Window.Resources>
<DockPanel>
<!--Menu bar on the left-->
<Border DockPanel.Dock="Left">
<StackPanel Orientation="Vertical" Background="Gray" Width="120">
<Button Content="Main" Command="{Binding ShowMainCommand}"/>
<Button Content="Settings" Command="{Binding ShowSettingsCommand}"/>
<Button Content="Exit" Command="{Binding ExitApplicationCommand}"/>
</StackPanel>
</Border>
<!--The content control that view the current view-->
<ContentControl Content="{Binding ApplicationViewModel}"/>
</DockPanel>
</Window>
Note: DataContext is set to ApplicationViewModel.cs in App.xaml.cs by overriding the OnStartup() method.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationWindow app = new ApplicationWindow
{
DataContext = new ApplicationViewModel()
};
app.Show();
}
}
Here's the ApplicationViewModel.cs:
public class ApplicationViewModel : ViewModelBase
{
#region Fields
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
private ICommand _showMainCommand;
private ICommand _showSettingsCommand;
private ICommand _exitApplicationCommmand;
#endregion
#region Constructor
public ApplicationViewModel()
{
ViewModels = new List<ViewModelBase>
{
new MainViewModel(),
new SettingsViewModel()
};
CurrentViewModel = ViewModels[0];
}
#endregion
#region Public Properties
public List<ViewModelBase> ViewModels
{
get
{
return _viewModels;
}
set
{
if (_viewModels != value)
{
_viewModels = value;
OnPropertyChanged(nameof(ViewModels));
}
}
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if(_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}
}
#endregion
#region Commands
public ICommand ShowMainCommand
{
get
{
if(_showMainCommand == null)
{
_showMainCommand = new RelayCommand(action => ShowMain());
}
return _showMainCommand;
}
}
public ICommand ShowSettingsCommand
{
get
{
if (_showSettingsCommand == null)
{
_showSettingsCommand = new RelayCommand(action => ShowSettings());
}
return _showSettingsCommand;
}
}
public ICommand ExitApplicationCommand
{
get
{
if (_exitApplicationCommmand == null)
{
_exitApplicationCommmand = new RelayCommand(action => ExitApplication());
}
return _exitApplicationCommmand;
}
}
#endregion
#region Private Methods
private void ShowMain()
{
CurrentViewModel = ViewModels[0];
}
private void ShowSettings()
{
CurrentViewModel = ViewModels[1];
}
private void ExitApplication()
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to exit?", "Exit", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
System.Windows.Application.Current.Shutdown();
}
}
#endregion
}
So, from what I understand, the ApplicationWindow.xaml should be able to determine which view to show out from what the CurrentViewModel is set to.
For the sake of information (or miss-information), here are ViewModelBase.cs:
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And RelayCommand.cs:
public class RelayCommand : ICommand
{
#region Fields
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
#endregion
#region ICommand
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion
}
I hope my thought process on this was clear to you, and that one of you smart programmers out there can help solving this, and help me understand why this isn't turning out as I want it to.
In case of what I'm trying to do is harder than Elon Musk's project on making life multiplanetary, feel free to explain why and suggest me a better way to
Your Content control binding should be pointed at the actual property you change when switching ViewModels
<ContentControl Content="{Binding CurrentViewModel}"/>
I searched through all other question with the same problem. But I can't find any solution from them.
OnPropertyChanged is firing but the Control is not updating. I'm using the Mahapps.Metro ProgressRing Control.
View Code
<controls:MetroWindow.Resources>
<userObj:LoginViewLogic x:Key="UserData"/>
<userObj:LoginViewLogic x:Key="LoginViewLogic"/>
</controls:MetroWindow.Resources>
<Grid>
<Canvas>
<controls:ProgressRing Name="ProgressRing" Canvas.Left="133" Canvas.Top="154" Height="50" Width="35" IsActive="{Binding Source={StaticResource UserData},Path=UserData.IsProgressRingActive}"/>
</Canvas>
</Grid>
ViewModel Code
public class LoginViewLogic {
public LoginViewLogic() {
_userData = new User(AppSettings.ReadCredentials(),(bool)loadedSettings);
}
private User _userData;
public User UserData
{
get { return _userData; }
set { _userData = value; }
}
public async void Login() {
_userData.IsProgressRingActive = true;
var loginResult = await Stuff.Login(_userData);
if (!loginResult) {
MessageBox.Show("You have entered an invalid username or password",
"Information", MessageBoxButton.OK, MessageBoxImage.Error);
_userData.IsProgressRingActive = false;
}
}
Model Code
public class User : INotifyPropertyChanged {
private bool _isProgressRingActive;
public bool IsProgressRingActive {
get { return _isProgressRingActive; }
set {
_isProgressRingActive = value;
OnPropertyChanged("IsProgressRingActive");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Have you tried this:
<controls:MetroWindow.Resources>
<userObj:LoginViewLogic x:Key="LoginViewLogic"/>
</controls:MetroWindow.Resources>
<Grid>
<Canvas>
<controls:ProgressRing Name="ProgressRing" Canvas.Left="133" Canvas.Top="154" Height="50" Width="35" IsActive="{Binding Source={StaticResource LoginViewLogic},Path=UserData.IsProgressRingActive}"/>
</Canvas>
</Grid>
So you are only creating and binding to one instance of LoginViewLogic class?
Your viewmodel should also raise a PropertyChanged event.
Try with below code.
public class LoginViewLogic : INotifyPropertyChanged
{
public LoginViewLogic()
{
_userData = new User(AppSettings.ReadCredentials(),(bool)loadedSettings);
}
private User _userData;
public User UserData
{
get { return _userData; }
set { _userData = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
So I have the following XAML:
<TextBlock Text="{Binding DisconnectedDevices, UpdateSourceTrigger=PropertyChanged}" />
The view model has the following properties:
public string DisconnectedDevices {get; set;}
public IEnumerable<IDeviceInformationVM> DeviceCollection {get; set;}
There's a method that gets called which raises the property notified event:
public void DeviceCollectionChanged()
{
RaisePropertyChanged(() => DeviceCollection);
}
I'd like to update the value in the TextBlock when DeviceCollection changes. I'm aware that I could just call RaisePropertyChanged on DisconnectedDevices but I'm wondering if its possible to update a TextBlock on a different property change event.
Thanks all!
EDIT: Thanks for the suggestions for using an ObservableCollection instead of IEnumerable, unfortunately, I'm not at liberty to change the collection type..
The DeviceCollectionChanged method is called whenever the collection changes (tedious I know...)
FURTHER EDIT:
Have just gone ahead with
RaisePropertyChanged(() => DisconnectedDevices);
I appreciate not enough information might have been provided in the question to get what I was trying to do, apologies for that
I am not sure if your current code works, but assuming it works.
Why not use - ObservableCollection<IDeviceInformationVM> instead of IEnumerable<IDeviceInformationVM> DeviceCollection you wont need the DeviceCollectionChanged event. It will be taken care.
Yes you can raise
public void DeviceCollectionChanged()
{
RaisePropertyChanged(() => DeviceCollection);
RaisePropertyChanged(() => DisconnectedDevices);
// or RaisePropertyChanged("DisconnectedDevices"); Whichever works
}
See this question, it might help you with implementation of NotifyPropertyChanged for multiple properties- WPF Notify PropertyChanged for a Get Property
Do you call the DeviceCollectionChanged() method everytime you change your DeviceCollection? How do you set DeviceCollection?
You can implement an ObservableCollection (bottom of this answer), or, depending on how you set your DeviceCollection, if for example DeviceCollection comes from a list, you can implement something like this:
private IEnumerable<IDeviceInformationVM> deviceCollection;
public IEnumerable<IDeviceInformationVM> DeviceCollection
{
get
{
return deviceCollection;
}
set
{
deviceCollection = value;
RaisePropertyChanged(() => DisconnectedDevices);
RaisePropertyChanged(() => DeviceCollection);
}
}
DeviceCollection = GetListOfIDeviceInformationVM(); //will automatically raise property changed and update your TextBlock
You won't have to keep on calling RaisePropertyChanged() which looks rather tedious
Change the type of the CollectionDevice collection to ObservableCollection then, raise the event CollectionChanged as follows :
DeviceCollection.CollectionChanged + = DeviceCollection_CollectionChanged;
I give you an implemention in MVVM with a class RelayCommand
here the view : (MainView)
<Window x:Class="WpfApplication.MainWindow"
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:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding DisconnectedDevices, Mode=TwoWay}" Height="25" Width="175" Grid.Row="0" />
<Button Grid.Row="1" Content="Click" Command="{Binding ToggleExecuteCommand}" Width="100" Height="25"/>
</Grid>
the ViewModel (Main ViewModel)
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace WpfApplication
{
public class MainViewModel : INotifyPropertyChanged
{
private string disconnectedDevices;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public MainViewModel()
{
ToggleExecuteCommand = new RelayCommand(ChangeCollection);
DeviceCollection = new ObservableCollection<DeviceInformationVM>();
DeviceCollection.CollectionChanged += DeviceCollection_CollectionChanged;
}
private void ChangeCollection(object obj)
{
DeviceCollection.Add(new DeviceInformationVM { MyProperty = "TEST" });
}
private void DeviceCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedAction action = e.Action;
if (action == NotifyCollectionChangedAction.Add)
{
DisconnectedDevices = "Somme thing added to collection";
}
if (action == NotifyCollectionChangedAction.Remove)
{
DisconnectedDevices = "Somme thing removed from collection";
}
}
public string DisconnectedDevices
{
get { return this.disconnectedDevices; }
set
{
if (value != this.disconnectedDevices)
{
this.disconnectedDevices = value;
NotifyPropertyChanged("DisconnectedDevices");
}
}
}
public ObservableCollection<DeviceInformationVM> DeviceCollection { get; set; }
public RelayCommand ToggleExecuteCommand { get; set; }
}
}
the RelayCommand :
using System;
using System.Windows.Input;
namespace WpfApplication
{
public class RelayCommand : ICommand
{
private Action execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
}
}
and finaly DeviceInformation
using System;
namespace WpfApplication
{
public interface IDeviceInformationVM
{
string MyProperty { get; set; }
}
public class DeviceInformationVM : IDeviceInformationVM
{
public string MyProperty
{
get; set;
}
}
}
Hope it helps