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));
}
}
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 am trying to set a common dependency property to two different user controls in WPF.
I tried lots of solutions that I found but none of them worked.
So for the moment what I got is the following:
I have a class containing the common property which currently (after trying almost everything) looks this way:
namespace CommonProperties {
public class CommonProp : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty IsTrueProperty =
DependencyProperty.Register("IsTrue", typeof(bool), typeof(CommonProp), new PropertyMetadata(false));
private bool _isTrue;
public bool IsTrue
{
get { return (bool)GetValue(IsTrueProperty); }
set { SetValue(IsTrueProperty, value); NotifyPropertyChanged("IsTrue"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
}}
I also have two user controls which looks like that: UC1:
<UserControl x:Class="ClassLibUC1.UC1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:CommonProperties;assembly=CommonProperties"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<TextBox x:Name="text1"
Width="254"
Height="23"
Margin="24,24,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding IsTrue}"
TextWrapping="Wrap" />
<Button Width="75"
Margin="70,68,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click"
Content="Button" />
</Grid>
UC1 ViewModel:
namespace ClassLibUC1 {
public class ViewModelUC1 : INotifyPropertyChanged
{
CommonProp prop = new CommonProp();
public bool IsTrue
{
get { return prop.IsTrue; }
set { prop.IsTrue = value; NotifyPropertyChanged("IsTrue"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Trigger the PropertyChanged event to update views.
/// </summary>
/// <param name="nomPropriete"></param>
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
}
UC1 Code:
public partial class UC1 : UserControl
{
ViewModelUC1 vm = new ViewModelUC1();
public UC1()
{
InitializeComponent();
this.DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CommonProp prop = new CommonProp();
if (vm.IsTrue)
{
vm.IsTrue = false;
}
else
{
vm.IsTrue = true;
}
}
}
The second user control is exactly the same. The problem is that when I click a button in the first or the second user control, it only updates the selected control and not both of them.. Any idea how can I implement a common property for both controls?
With Dependency property we cannot achieve this because Dependency property belongs to one instance, means if you have two instance of user control, updating DependencyProperty value of one control will never update DependencyProperty of other Control as it is a different instance all together.
But still we can achieve that requirement of updating Same value for all usercontrols of same type like yours. For that we have to do some modification in xaml.cs file like below.
/// <summary>
/// Interaction logic for UC1.xaml
/// </summary>
public partial class UC1 : UserControl
{
private static List<UC1> _allInstanceOfThisControl = new List<UC1>();
ViewModelUC1 vm = new ViewModelUC1();
public UC1()
{
InitializeComponent();
this.DataContext = vm;
_allInstanceOfThisControl.Add(this);
this.Unloaded += UC1_Unloaded;
}
private void UC1_Unloaded(object sender, RoutedEventArgs e)
{
_allInstanceOfThisControl.Remove(this);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (vm.IsTrue)
{
vm.IsTrue = false;
}
else
{
vm.IsTrue = true;
}
var _allInstanceOfThisControlExceptthis = _allInstanceOfThisControl.Where(s => s != this).ToList();
_allInstanceOfThisControlExceptthis.ForEach(s =>
{
(s.DataContext as ViewModelUC1).IsTrue = vm.IsTrue;
});
}
Hope this will help to achieve your requirement.
I have a UserControl that I add to my main application.
That UserControl contains a button for a UIElement
The UserControl contains a DispatchTimer and every 2 seconds based on some int values determines what the button image will be.
One of the methods called in the UserControl should set it's image but the control never displays the image that it was changed to.
public void SetNormal()
{
btnFlashAlert.Content = new BitmapImage(new Uri("Images/FlashButton.png", UriKind.RelativeOrAbsolute));
}
Is there something i'm missing to get the look of the control update on the main application?
When I look at what .Content contains, it is correct. The UI doesn't reflect the change.
XAML
<UserControl x:Class="SC.FlashSystem.MainButton"
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" Height="53" Width="164">
<Button x:Name="btnFlashAlert" Background="{x:Null}" BorderBrush="{x:Null}" Cursor="Hand" Click="btnFlashAlert_Click">
<Button.Template>
<ControlTemplate>
<Image Source="Images/FlashButton.png"/>
</ControlTemplate>
</Button.Template>
</Button>
Codebehind Updated
public partial class MainButton : UserControl
{
private SupportConsoleWeb.MessageData messageCounts { get; set; }
private readonly DispatcherTimer flashButtonChangeTimer = new DispatcherTimer();
private BitmapImage NormalImage { get; set; }
private BitmapImage CriticalImage { get; set; }
private BitmapImage AlertImage { get; set; }
private BitmapImage InfoImage { get; set; }
public MainButton()
{
InitializeComponent();
messageCounts = new SupportConsoleWeb.MessageData();
messageCounts.CriticalCount = 0;
messageCounts.AlertCount = 0;
messageCounts.InfoCount = 0;
NormalImage = new BitmapImage(new Uri("Images/FlashButton.png", UriKind.RelativeOrAbsolute));
CriticalImage = new BitmapImage(new Uri("Images/FlashButtonRed.png", UriKind.RelativeOrAbsolute));
AlertImage = new BitmapImage(new Uri("Images/FlashButtonOrange.png", UriKind.RelativeOrAbsolute));
InfoImage = new BitmapImage(new Uri("Images/FlashButtonGreen.png", UriKind.RelativeOrAbsolute));
flashButtonChangeTimer.Interval = TimeSpan.FromSeconds(2);
flashButtonChangeTimer.Tick += flashButtonChangeTimer_Tick;
flashButtonChangeTimer.Start();
}
void flashButtonChangeTimer_Tick(object sender, EventArgs e)
{
btnFlashAlert.Dispatcher.BeginInvoke(new Action(() =>
{
if (btnFlashAlert.Content == null)
{
SetNormal();
}
else if (messageCounts.CriticalCount > 0 && btnFlashAlert.Content.Equals(CriticalImage))
{
SetNormal();
}
else if (messageCounts.AlertCount > 0 && btnFlashAlert.Content.Equals(AlertImage))
{
SetNormal();
}
else if (messageCounts.InfoCount > 0 && btnFlashAlert.Content.Equals(InfoImage))
{
SetNormal();
}
else if (messageCounts.CriticalCount > 0)
{
SetCritical();
}
else if (messageCounts.AlertCount > 0)
{
SetAlert();
}
else if (messageCounts.InfoCount > 0)
{
SetInfo();
}
}));
}
public void UpdateMessageCounts(SupportConsoleWeb.MessageData messageCounts)
{
this.messageCounts = messageCounts;
}
private void btnFlashAlert_Click(object sender, RoutedEventArgs e)
{
MainWindow window = new MainWindow();
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
window.ShowDialog();
}
public void SetMessageCount(int criticalCount, int alertCount, int infoCount)
{
messageCounts.CriticalCount = criticalCount;
messageCounts.AlertCount = alertCount;
messageCounts.InfoCount = infoCount;
}
private void SetNormal()
{
btnFlashAlert.Content = NormalImage;
}
private void SetCritical()
{
btnFlashAlert.Content = CriticalImage;
}
private void SetAlert()
{
btnFlashAlert.Content = AlertImage;
}
private void SetInfo()
{
btnFlashAlert.Content = InfoImage;
}
}
Change your XAML To this
<Image Source="{Binding TheImage}"/>
Add notify property changed
public partial class MainButton : UserControl, INotifyPropertyChanged
Create the OnPropertyChanged Event
void OnPropertyChanged(String prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
Create a Bitmap prop and notify the prop changed event
private BitmapImage _TheImage;
public BitmapImage TheImage
{
get { return _TheImage; }
set { _TheImage = value; OnPropertyChanged("TheImage"); }
}
In your initializer
public MainButton()
{
this.DataContext = this;
InitializeComponent();
TheImage = new BitmapImage();
Now in your setting methods call
TheImage = //Your Bitmap Goes here
I know this seems excessive but you will see it is a much cleaner implementation in the long run.
I believe its an issue with picture selection logic not having a default image when none of the conditions are met...
With that said, IMHO the picture logic would be better expressed by having all images pre-loaded and their visibility initially set to hidden. Then bind the visibility of each image to a specific flag boolean on the VM. Which the timer event can simply turn on or off the boolean(s) which will ultimately show or hide images as needed.
That removes any latency due to loading and showing of images for they will be pre-loaded; also it will solve any possible future memory issues due to loading/unloading of images.
Example
The following example has a button with two images. Both image's visibility is bound to Booleans on the VM. The VM has one Boolean which the imageas work off of and a timer which changes its status every two seconds switching the images.
Xaml:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Button x:Name="bStatus" Width="48" Height="48">
<StackPanel Orientation="Vertical">
<Image Source="Images\Copy-icon.png" Visibility="{Binding IsCopyOn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
<Image Source="Images\Recycle-icon.png"
Visibility="{Binding IsRecycleOn,
Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</Button>
VM
public class MainVM : INotifyPropertyChanged
{
private bool _bSwitch;
private readonly DispatcherTimer flashButtonChangeTimer = new DispatcherTimer();
public bool IsRecycleOn
{
get { return _bSwitch; }
}
public bool IsCopyOn
{
get { return !_bSwitch; }
}
public MainVM()
{
flashButtonChangeTimer.Interval = TimeSpan.FromSeconds(2);
flashButtonChangeTimer.Tick += (sender, args) =>
{
_bSwitch = ! _bSwitch;
OnPropertyChanged("IsCopyOn");
OnPropertyChanged("IsRecycleOn");
};
flashButtonChangeTimer.Start();
}
/// <summary>Event raised when a property changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Raises the PropertyChanged event.</summary>
/// <param name="propertyName">The name of the property that has changed.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
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.