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.
Related
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'm learning WPF MVVM and I get stuck with one problem. I'm calculating variable value in one class every 200ms, then I'm sending it to ViewModel where PropertyChanged event is fired, but the value did not appear on View. When I copy this code for calculation of variable value to ViewModel constructor then everything is working. Could you give me some hints what am I doing wrong?
Class where I calculate values:
public class CalculatingSecondsTask
{
public string seconds { get; set; }
public CalculatingSecondsTask()
{
Task.Run(async () =>
{
int i = 0;
while (true)
{
await Task.Delay(200);
seconds = (i++).ToString();
UpdateSecondsInViewModel(seconds);
}
});
}
public void UpdateSecondsInViewModel(string secs)
{
ViewModel test = new ViewModel();
test.Seconds_To_Start = secs;
}
My ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private string mSeconds_To_Start;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (propertyName == null)
throw new ArgumentNullException("propertyExpression");
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Seconds_To_Start
{
get
{
return mSeconds_To_Start;
}
set
{
mSeconds_To_Start = value;
OnPropertyChanged(nameof(Seconds_To_Start));
}
}
And my View is like:
<Window x:Class="Program.Views.StartWindow"
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:Program.Views"
xmlns:ViewModel="clr-namespace:Program.ViewModels"
mc:Ignorable="d"
Title="Program" Height="110" Width="400">
<Window.DataContext>
<ViewModel:ViewModel/>
</Window.DataContext>
<Grid>
<!-- Text -->
<TextBlock x:Name="Seconds_To_Start" HorizontalAlignment="Center">
<Run Text="{Binding Seconds_To_Start}" />
<Run Text="seconds" />
</TextBlock>
</Grid>
</Window>
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
}
I'm trying to Bind the Text property of the TextBlock to StaticClass.Percent. Since I couldn't do that (Or could I?) I have defined the LoadingPercent in my ViewModel so that I can bind to it. It is still not working. How can I solve this? Or can I bind to the StaticClass directly and ignore the ViewModel approach?
<Window x:Class="TestBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:testBinding="clr-namespace:TestBinding"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<testBinding:ViewModel/>
</Window.DataContext>
<StackPanel>
<TextBlock Width="100"
HorizontalAlignment="Center"
Text="{Binding LoadingPercent}"/>
<Button Content="Change"
Width="200"
Height="30"
Margin="0 20 0 0"
HorizontalAlignment="Center"
Click="ChangeText"/>
</StackPanel>
</Window>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void ChangeText(object sender, RoutedEventArgs e)
{
StaticClass.Percentage = 10;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double loadingPercent;
public double LoadingPercent
{
get { return StaticClass.Percentage; }
set
{
loadingPercent = value;
RaisePropertyChanged("LoadingPercent");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public static class StaticClass
{
public static int Percentage { get; set; }
}
Here is a mistake:
private double loadingPercent;
public double LoadingPercent
{
get { return StaticClass.Percentage; }
set
{
loadingPercent = value;
RaisePropertyChanged("LoadingPercent");
}
}
You return in get the StaticClass.Percentage but you assign loadingPercent in set.
I am not sure why you need the static class after all, but if you want to ditch the viewmodel and bind directly to the static property, see here
I bind a class which derived from INotifyPropertyChange to a Datacontext.
after some interaction, a value will be calculated and output property will be updated.
My problem is that the result textbox didn't update at all.
public partial class setraSubWpfTolerance : UserControl
{
public setraFit objSource = new setraFit();
public setraSubWpfTolerance()
{
InitializeComponent();
this.DataContext = objSource;
}
}
And the class:
public class setraFit : INotifyPropertyChanged
{
private readonly CollectionView _BoreSystems;
public CollectionView BoreSystems
{
get { return _BoreSystems; }
}
private decimal? _MaxBoreDimension;
public decimal? MaxBoreDimension
{
get { return _MaxBoreDimension; }
set
{
if (_MaxBoreDimension == value) return;
_MaxBoreDimension = value;
onPropertyChanged("MaxBoreDimension");
}
}
private string _BoreSystem;
public string BoreSystem
{
get { return _BoreSystem; }
set
{
if (_BoreSystem == value) return;
_BoreSystem = value;
calcBoreDimension();
onPropertyChanged("BoreSystem");
}
}
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
}
Last but not least the XAML
<UserControl x:Class="SetraSubForms.setraSubWpfTolerance"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="375">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="194,10,0,0" Name="BoreSystemComboBox" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=BoreSystems}"
SelectedValue="{Binding Path=BoreSystem}"/>
<TextBox HorizontalAlignment="Left" Margin="194,67,0,37" Name="MaxDimBoreTextBox" Width="120" IsReadOnly="False"
Text="{Binding Path=MaxBoreDimension, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Grid>
</UserControl>
I expected to receive the dummy value of 100.035 after changing the combobox but the textbox did not update. If i run step by step i can see the "MaxBoreDimension" property of setraFit is changed.
What did i do wrong?
Thanks in advance for your help
sittingDuck
Your method is updating the private value, not the Property:
private void calcBoreDimension()
{
_MaxBoreDimension = (decimal)100.035;
}
Change to
private void calcBoreDimension()
{
MaxBoreDimension = (decimal)100.035;
}
You're doing the same thing in the constructor, which is causing your calcBoreDimension method to not run:
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
_BoreSystems = new CollectionView(listBore);
}
should be
public setraFit()
{
IList<string> listBore = setraStaticTolerance.getBoreList();
BoreSystems = new CollectionView(listBore); //this line!
}
When you create properties that point to private fields, you should almost never have to set the private field anywhere other than the property. This is why properties exist- so that whenever you get or set them, you will run the code in the get and set blocks instead of just retrieving the current value.
SOLVED!
The key is to initate the PropertyChanged event for the "MaxBoreDimension"
public decimal? NominalDimension
{
get { return _NominalDimension; }
set
{
if (_NominalDimension == value) return;
_NominalDimension = value;
calcBoreDimension();
onPropertyChanged("NominalDimension");
onPropertyChanged("MaxBoreDimension");
}
}
Thanks DLeh for the contribution.