I created a custom control with of property consisting in a list of a custom type (list<OHLCV>). I am using a dependency property to allow it to be bindable.
Here is my code behind
public partial class GraphControl : UserControl
{
//OHLCVSerie Property
public List<OHLCV> OHLCVSerie
{
get { return (List<OHLCV>)GetValue(OHLCVSerieProperty); }
set { SetValueDP(OHLCVSerieProperty, value); }
}
public static readonly DependencyProperty OHLCVSerieProperty =
DependencyProperty.Register("OHLCVSerie", typeof(List<OHLCV>), typeof(GraphControl), null);
//reuse
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDP(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String p = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public GraphControl()
{
InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
}
The XAML has not yet been modified (= the user control is empty, except for the code behind)
In my main window, I created an instance of my custom control, and I bound to it a list<OHLCV>
<Window x:Class="MarketAnalyzer.Tester.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:grph="clr-namespace:MarketAnalyzer.DataVisualization;assembly=MarketAnalyzer.DataVisualization"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<grph:GraphControl OHLCVSerie="{Binding OHLCVSerie}" Margin="0,41,0,0"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
The code behind creates the list<OHLCV> at the initialization of the MainWindow, while the button modifies the list if clicked.
public partial class MainWindow : Window, INotifyPropertyChanged
{
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The OHLCV Serie
/// </summary>
private List<OHLCV> _ohlcvserie;
public List<OHLCV> OHLCVSerie
{
get { return _ohlcvserie; }
set
{
if (_ohlcvserie != value)
{
_ohlcvserie = value;
RaisePropertyChanged("OHLCVSerie");
}
}
}
public MainWindow()
{
InitializeComponent();
OHLCVSerie = new List<OHLCV>();
OHLCVSerie = CreateRandomOHLCV(1, new DateTime(2016, 1, 1, 9, 0, 0), 4000, 100);
this.DataContext = new
{
OHLCVSerie,
};
}
/// <summary>
/// Generate a random serie following usual index distribution parameters
/// </summary>
/// <param name="MinuteStep">number of minutes between each tick</param>
/// <param name="StartDate">starting date</param>
/// <param name="StartValue">starting value at tick 0</param>
/// <param name="N">Number of ticks</param>
/// <returns></returns>
public List<OHLCV> CreateRandomOHLCV(int MinuteStep, DateTime StartDate, double StartValue, int N)
{
List<OHLCV> RandomOHLCV = new List<OHLCV>();
//whatever code that create my random list
return RandomOHLCV;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
OHLCVSerie = CreateRandomOHLCV(1, new DateTime(2016, 1, 1, 9, 0, 0), 4000, 1000);
}
If I go to check the value of my list in my custom control I see that it is correctly implemented at the initialization of the MainWindow (a value is passed, with 100 items in the list), but it is not updated when I click the button (still the same list with 100 items while the button click create a list with 1.000 items).
How can I have the list updated in my custom control when the corresponding list is changed in my MainWindow?
You are setting your DataContext to the current instance of OHLCVSerie, try to set your DataContext to this (your MainWindow).
Related
I'm trying to make two WPF projects and a DLL project in my solution following MVVM.
A first WPF to be used as a management panel for the second WPF (ex: you write text, press a button and the text is displayed in the second WPF Window).
I want to put my Models in the DLL.
My problem is that i don't know how to display the text (notify ?) in the second WPF.
I implemented INotifyPropertyChanged in the viewmodel.
And then I'm stuck, I don't know what to do...
My Solution looks like this :
WPF_Solution
DisplayWPF
MainWindow.xaml
Dll_mvvm
Text.cs :
ManagementWPF
DisplayViewModel.cs
MainWindow.xaml
Both Display and Management refer to the DLL.
Text.cs :
public class Text
{
string _textToDisplay;
/// <summary>
/// Text to display on screen
/// </summary>
public string TextToDisplay
{
get { return _textToDisplay; }
set { _textToDisplay = value; }
}
}
DisplayViewModel.cs :
public class DisplayViewModel : INotifyPropertyChanged
{
private Text _text;
/// <summary>
/// Constructs the default instance of a ToDisplayViewModel
/// </summary>
public DisplayViewModel()
{
_text = new Text();
}
/// <summary>
/// Accessors
/// </summary>
public Text Text
{
get { return _text; }
set { _text = value; }
}
public string TextToDisplay
{
get { return Text.TextToDisplay; }
set
{
if (Text.TextToDisplay != value)
{
Text.TextToDisplay = value;
RaisePropertyChanged("TextToDisplay");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Management Panel :
public partial class MainWindow : Window
{
private DisplayViewModel _displayThings;
private Affichage.MainWindow _displayer = new Affichage.MainWindow();
public MainWindow()
{
InitializeComponent();
_displayThings = (DisplayViewModel)base.DataContext;
_displayer.Show();
}
private void disp_btn_Click(object sender, RoutedEventArgs e)
{
_displayThings.TextToDisplay = textBox.Text;
}
}
And the WPF are just a button and a textbox in the Control window, and a textbox in the Display window. Linked to the viewmodel
<Window.DataContext>
<!-- Declaratively create an instance of DisplayViewModel -->
<local:DisplayViewModel />
</Window.DataContext>
The TextBox is binded with Text="{Binding TextToDisplay}"
Should my Viewmodel be share din the DLL too ?
How to notify the other project that ther was a change in the model ?
Remove the following markup as it will create a new instance of the DisplayViewModel class:
<Window.DataContext>
<!-- Declaratively create an instance of DisplayViewModel -->
<local:DisplayViewModel />
</Window.DataContext>
...and assign the DataContext property of the child window to the instance of the DisplayViewModel class that you are actually setting the TextToDisplay property of:
public partial class MainWindow : Window
{
private DisplayViewModel _displayThings = = new DisplayViewModel();
private Affichage.MainWindow _displayer = new Affichage.MainWindow();
public MainWindow()
{
InitializeComponent();
_displayer.DataContext = _displayThings;
_displayer.Show();
}
private void disp_btn_Click(object sender, RoutedEventArgs e)
{
_displayThings.TextToDisplay = textBox.Text;
}
}
I have a XAML and CS file for a UserControl. I have my data stored in a Singleton class which implements INotifyPropertyChanged, and it binds to a ListBox in the UserControl.
This is the XAML databinding:
<ListBox Name="ModsListBox"
ItemsSource="{Binding ModControls}"
Visibility="Visible"
Width="350"
Height="Auto">
</ListBox>
The datacontext is being set in the CS file as followings:
DataContext = ModDirector.Instance;
InitializeComponent();
In the code there is a method for adding elements which adds the datastructure which is being bound and then calls OnPropertyChanged(), however the UI never updates.
/// <summary>
/// Adds a mod and sends event to update UI elements bound to ModContols
/// </summary>
/// <param name="modUserControl"></param>
/// <param name="index"></param>
public void AddMod(ModUserControl modUserControl, int? index = null)
{
if (index != null)
{
_modControls.Insert(index.Value, modUserControl);
}
else
{
_modControls.Add(modUserControl);
}
OnPropertyChanged("ModControls");
}
Just for completion here is the property it is being bound to:
/* Properties */
public List<ModUserControl> ModControls
{
get { return _modControls; }
set
{
_modControls = value;
OnPropertyChanged();
}
}
/* End Properties */
And the code for OnPropertyChanged
/// <summary>
/// Fires PropertyChanged event notifying the UI elements bound
/// </summary>
/// <param name="propertyName"></param>
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Is there some reason the event would not be propagated?
Change your List<ModUserControl> to ObservableCollection<ModUserControl>.
Your public List<ModUserControl> ModControls should probably be an ObservableCollection<> instead, so you can remove your manual calls to OnPropertyChanged("ModControls");. Your ModControls did not actually change. It's still the very same instance.
I have a problem when UI does not update to changing in variables, that are binded to control properties.
Help me understand why.
1) I have a class which inherited from UserControl and from InotifyPropertyChanged
public class BindableControl:UserControl, INotifyPropertyChanged
{
#region Data
private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
private const string ERROR_MSG = "{0} is not a public property of {1}";
#endregion // Data
#region Constructors
static BindableControl()
{
eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
}
protected BindableControl()
{
}
#endregion // Constructors
#region Public Members
/// <summary>
/// Raised when a public property of this object is set.
/// </summary>
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Returns an instance of PropertyChangedEventArgs for
/// the specified property name.
/// </summary>
/// <param name="propertyName">
/// The name of the property to create event args for.
/// </param>
public static PropertyChangedEventArgs
GetPropertyChangedEventArgs(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentException(
"propertyName cannot be null or empty.");
PropertyChangedEventArgs args;
// Get the event args from the cache, creating them
// and adding to the cache if necessary.
lock (typeof(BindableObject))
{
bool isCached = eventArgCache.ContainsKey(propertyName);
if (!isCached)
{
eventArgCache.Add(
propertyName,
new PropertyChangedEventArgs(propertyName));
}
args = eventArgCache[propertyName];
}
return args;
}
#endregion // Public Members
#region Protected Members
/// <summary>
/// Derived classes can override this method to
/// execute logic after a property is set. The
/// base implementation does nothing.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected virtual void AfterPropertyChanged(string propertyName)
{
}
/// <summary>
/// Attempts to raise the PropertyChanged event, and
/// invokes the virtual AfterPropertyChanged method,
/// regardless of whether the event was raised or not.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
this.VerifyProperty(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
// Get the cached event args.
PropertyChangedEventArgs args =
GetPropertyChangedEventArgs(propertyName);
// Raise the PropertyChanged event.
handler(this, args);
}
this.AfterPropertyChanged(propertyName);
}
#endregion
#region Private Helpers
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = this.GetType();
// Look for a public property with the specified name.
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// The property could not be found,
// so alert the developer of the problem.
string msg = string.Format(
ERROR_MSG,
propertyName,
type.FullName);
Debug.Fail(msg);
}
}
#endregion
}
2) Then I have another classes, each of them inherited from BindableControl like this
public class CameraLocalization : BindableControl
{
public CameraLocalization()
{
headers = new CameraHeaders();
toolTips = new CameraToolTips();
SetRuLocal();
//SetEnLocal();
}
private Language lang = SettingsManager.Language.ru_RU;
private CameraHeaders headers;
private CameraToolTips toolTips;
public Language Lang
{
get { return lang; }
set
{
lang = value;
SetLocal();
RaisePropertyChanged();
}
}
3) In XAML I link this class as usercontrol and do binding like this:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid>
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<GroupBox Header="{Binding ElementName=Localization, Path=Headers.PositionGroupHeader, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
4) from another page I try to change language:
xmlns:language ="clr-namespace:SettingsManager.Localization.Camera"
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Width="Auto" Margin="0,0,0,5">
<language:CameraLocalization x:Name="Localization"></language:CameraLocalization>
<ComboBox ItemsSource="{Binding Source={StaticResource Language}}" Width="70" HorizontalAlignment="Left"
SelectedValue="{Binding ElementName=Localization, Path=Lang, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBox>
And nothing happens. In debug mode i see that value of propertis changing, but they doesnot update on UI. What is the problem here? Who knows?
Your properties should NOT be declared in the UI elements (UserControl), and these should NOT implement INotifyPropertyChanged. You must separate UI from data/logic by using the MVVM Pattern.
You should create a proper ViewModel and put your properties and property change notification there.
The mainpage:
MainPage.xaml
<Canvas x:Name="LayoutRoot" Background="White">
</Canvas>
MainPage.xaml.cs
List<Usol> list = new List<Usol>();
for (int i = 0; i < 10; i++)
{
var element = new Usol();
list.Add(element);
Canvas.SetTop(element, i * 25);
LayoutRoot.Children.Add(list[i]);
}
foreach (var item in list)
{
item.context.name = "Varken";
}
A usercontrol
Usol.xaml
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Name}" />
</Grid>
Usol.xaml.cs
public Context context;
public Usol()
{
InitializeComponent();
context = new Context();
this.DataContext = context;
}
A class
Context.cs
public class Context : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region Fields
/// <summary>
/// Field Declaration for the <see cref="Name"/>
/// </summary>
private string name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the Name
/// </summary>
public string Name
{
get { return name; }
set
{
if (this.name != value)
{
this.name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
}
Situation
I have created this small test application to copy a problem I have in a bigger application. It works about the same way (not exactly, but close enough).
It adds several custom made usercontrols and each get a own instance of a datacontext class.
However, none of the properties are willing to update themselfs due to a empty PropertyChangedEventHandler.
Question
Why is public event PropertyChangedEventHandler PropertyChanged; always null?
Context.cs needs to implement INotifyPropertyChanged interface. Are you doing that?
Edit: Post your update.
I have generally seen this kind of problem when programmers create "two" instances of Model/ViewModel. While you attach one instance with View, it's always the other one that gets update (which ofcourse will have a null PropertyChanged subscribers). Thus, you must make sure that your view is using the same instance as being updated at other parts.
Hope my point is clear.
Your code is wrong,
OnPropertyChanged("Name"); <-- should update "name" not "Name"
You are firing event saying that "Name" is changed, but name of property is "name", C# and binding are case sensitive.
Change it to,
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged("Name");
}
}
}
#endregion
From C# 6 on wards, please use nameof() keyword...
#region Fields
/// <summary>
/// Field Declaration for the <see cref="name"/>
/// </summary>
private string _Name;
#endregion
#region Properties
/// <summary>
/// Gets or Sets the name
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
#endregion
I'm gonna write a code with DataBinding and a Timer to change an Image sequentially.
e.g: in each two seconds.
below is my C# code :
public class GenerateRandomImagePath
{
Random random = new Random((int)DateTime.Now.Ticks);
readonly int MinInt;
readonly int MaxInt;
readonly string PrefixImagesName;
readonly string ImageExtension;
/// <summary>
/// Used in data binding
/// </summary>
public string ImageFullPath { get; set; }
public GenerateRandomImagePath(string prefixName, string extension)
{
this.PrefixImagesName = prefixName;
this.MinInt = 1;
this.MaxInt = 100;
this.ImageExtension = extension;
}
int RandomNumber()
{
return random.Next(this.MinInt, this.MaxInt);
}
public void GenerateNewRandomImagePath()
{
this.ImageFullPath = this.PrefixImagesName + RandomNumber() + this.ImageExtension;
}
}
public partial class MainWindow : Window
{
GenerateRandomImagePath RandomImagePath;
System.Timers.Timer timer = new System.Timers.Timer(2000);
public MainWindow()
{
InitializeComponent();
RandomImagePath = new GenerateRandomImagePath(#"C:\Users\MDS\Pictures\Nature 02\Nature ", #".jpg");
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Enabled = true;
this.DataContext = RandomImagePath;
RandomImagePath.GenerateNewRandomImagePath();//this line works well
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
RandomImagePath.GenerateNewRandomImagePath();
}
}
The XAML code :
<Window
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"
x:Class="sth.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="800" Height="600" mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Image Source="{Binding Path=ImageFullPath}" />
</Grid>
</Window>
The code works just for first time! After that, the timer changes ImageSource but it doesn't effect on view!
Would you please guide me?
Thanks
According to your code, the GenerateRandomImagePath class doesn't implement INotifyPropertyChanged. WPF can't know that the ImageFullPath has changed unless you tell it, either by implementing that interface or by changing the class to derive from DependencyObject and turning the property into a dependency property.
I would suggest implementing INotifyPropertyChanged - it's a more lightweight approach.
public class GenerateRandomImagePath : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
private string _imageFullPath;
public string ImageFullPath
{
get { return _imageFullPath; }
set
{
_imageFullPath = value;
OnPropertyChanged("ImageFullPath");
}
}
}