Making a AnimationQueue - wpf

I have been trying to make a Ellipse to follow a given trajectory with the DoubleAnimation class. So far not too good.
I made a custom class AnimationQueue :
private class AnimationQueue
{
private List<DoubleAnimation> animation1 ;
private DependencyProperty property1;
private bool running1;
private List<DoubleAnimation> animation2;
private DependencyProperty property2;
private bool running2;
private int curent;
private UIElement element;
public AnimationQueue(UIElement element , DependencyProperty property )
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property;
}
public AnimationQueue(UIElement element, DependencyProperty property1 , DependencyProperty property2)
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property1;
this.property2 = property2;
}
public void queueAnimation(DoubleAnimation animation1, DoubleAnimation animation2)
{
animation1.Completed += (s, e) =>
{
running1 = false;
};
animation2.Completed += (s, e) =>
{
running2 = false;
};
this.animation1.Add(animation1);
this.animation2.Add(animation2);
}
public void start(int i)
{
if (animation1.Count != animation2.Count)
Console.WriteLine("Animation Queue not equal");
else
{
if (i == animation1.Count)
Console.WriteLine("Animation finished");
else if (i <= animation1.Count && i <= animation2.Count)
{
element.BeginAnimation(property1, animation1[i]);
element.BeginAnimation(property2, animation2[i]);
if (running1 == false && running2 == false)
{
i++;
start(i);
}
}
}
}
public void start()
{
curent = 0;
element.BeginAnimation(property1, animation1[curent]);
element.BeginAnimation(property2, animation2[curent]);
if(running1 == false && running2 == false)
start(curent++);
}
}`
In this class I add 2 animations for each point in the coordinate list . 1 to animate the x axis movement and 1 to anymate the y axis movement . The adding goes like this :
public void StartCourse()
{
int i = 0;
List<Point> coordinates = course.firstFuntion();
while (coordinates.Count != i)
{
queue1.queueAnimation(new DoubleAnimation(coordinates[i].X, new Duration(TimeSpan.FromMilliseconds(500))),
new DoubleAnimation(coordinates[i].Y, new Duration(TimeSpan.FromMilliseconds(500)))
);
i++;
}
queue1.start();
}
My problem is , I believe , in the AnimationQueue.Start() method. The program stars the animation , however it only executes the first animation . After that it stops . I tried to make use of the animation.Completed Event Handler but to no success.

BeginAnimation starts animation asynchrously. I suppose this may help:
private class AnimationQueue
{
private List<DoubleAnimation> animation1 ;
private DependencyProperty property1;
private bool running1;
private List<DoubleAnimation> animation2;
private DependencyProperty property2;
private bool running2;
private int curent;
private UIElement element;
public AnimationQueue(UIElement element , DependencyProperty property )
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property;
}
public AnimationQueue(UIElement element, DependencyProperty property1 , DependencyProperty property2)
{
curent = -1;
this.element = element;
animation1 = new List<DoubleAnimation>();
animation2 = new List<DoubleAnimation>();
this.property1 = property1;
this.property2 = property2;
}
public void queueAnimation(DoubleAnimation animation1, DoubleAnimation animation2)
{
this.animation1.Add(animation1);
this.animation2.Add(animation2);
animation1.Completed += (s, e) =>
{
if (this.animation1.Contains(animation1))
{
int index1 = this.animation1.IndexOf(animation1);
if (index1 + 1 < this.animation1.Count)
{
element.BeginAnimation(property1, animation1[index1 + 1]);
}
}
};
animation2.Completed += (s, e) =>
{
if (this.animation2.Contains(animation2))
{
int index2 = this.animation2.IndexOf(animation2);
if (index2 + 1 < this.animation2.Count)
{
element.BeginAnimation(property2, animation2[index2 + 1]);
}
}
};
}
public void start()
{
curent = 0;
element.BeginAnimation(property1, animation1[curent]);
element.BeginAnimation(property2, animation2[curent]);
}
}`

Related

Winforms: Controls not visible after added programmatically

I want to create a simple FileExplorer Control for a video player.
Here is the code:
namespace CustomControls
{
public interface IScrollable
{
float visiblePercent { get; set; }
float ScrollSpeedMultiplier { get; set; }
int scrollValue { get; set; }
CustomScrollBar scrollBar { get; set; }
}
}
namespace CustomControls {
public partial class ScrollablePanel : UserControl, IScrollable {
private int _scrollValue;
private int _totalWheelDelta;
private int totalWheelDelta {
get => _totalWheelDelta;
set {
SetTotalDelta(value);
}
}
public int Count => panelInner.Controls.Count;
public bool VisibleControls => panelInner.Controls.OfType<Panel>().All(p => p.Visible);
public float visiblePercent { get; set; }
public int scrollValue {
get { return _scrollValue; }
set {
_scrollValue = value;
if (scrollBar != null && scrollBar.value != _scrollValue)
scrollBar.value = _scrollValue;
SetTotalDeltaByScrollValue(value);
ScrollThrough();
}
}
[EditorBrowsable(EditorBrowsableState.Always), Browsable(true), DefaultValue(0), Category("Behavior"), Description("Die dazugehörige Scrollbar")]
public CustomScrollBar scrollBar { get; set; }
public float ScrollSpeedMultiplier { get; set; }
public ScrollablePanel() {
InitializeComponent();
panelInner.MouseWheel += panelInner_MouseWheel;
}
protected override ControlCollection CreateControlsInstance() {
return new ScrollablePanelElementCollection(this, this.panelInner);
}
private void ScrollablePanel_Load(object sender, EventArgs e) {
OnControlsChanged();
}
private void panelInner_MouseWheel(object sender, MouseEventArgs e) {
if (visiblePercent >= 1.0f)
return;
totalWheelDelta += (int)(e.Delta * ScrollSpeedMultiplier);
scrollValue = (100 * SystemInformation.MouseWheelScrollLines * totalWheelDelta) / ((Height - panelInner.Height) * SystemData.scrollDeltaModifier);
if (scrollBar != null)
scrollValue = scrollBar.value = (100 * SystemInformation.MouseWheelScrollLines * totalWheelDelta) / ((Height - panelInner.Height) * SystemData.scrollDeltaModifier);
}
private void ScrollThrough() {
panelInner.Location = new Point(0, (int)((Height - panelInner.Height) * (scrollValue / 100.0f)));
}
private void panelInner_ControlAdded(object sender, ControlEventArgs e) {
OnControlsChanged();
}
private void panelInner_ControlRemoved(object sender, ControlEventArgs e) {
OnControlsChanged();
}
private void OnControlsChanged() {
int height = int.MinValue;
foreach (Control control in panelInner.Controls)
if (control.Visible)
height = Math.Max(height, control.Bottom);
panelInner.Height = height;
ScrollablePanel_Resize(this, new EventArgs());
if (panelInner.Height > 0)
visiblePercent = (float)Height / panelInner.Height;
else
visiblePercent = 1.0f;
if (scrollBar != null)
scrollBar.largeChange = (int)(visiblePercent * 100);
}
private void ScrollablePanel_Resize(object sender, EventArgs e) {
panelInner.Width = Width;
}
private void SetTotalDelta(int value) {
int lines = SystemInformation.MouseWheelScrollLines * (value / SystemData.scrollDeltaModifier);
int deltaHeight = Height - panelInner.Height;
if (lines > 0)
_totalWheelDelta = 0;
else if (lines < deltaHeight)
_totalWheelDelta = (deltaHeight * SystemData.scrollDeltaModifier) / SystemInformation.MouseWheelScrollLines;
else
_totalWheelDelta = value;
}
private void SetTotalDeltaByScrollValue(int value) {
int deltaHeight = Height - panelInner.Height;
totalWheelDelta = (deltaHeight * SystemData.scrollDeltaModifier * scrollValue) / (100 * SystemInformation.MouseWheelScrollLines);
}
public void Clear() {
panelInner.Controls.Clear();
}
}
public class ScrollablePanelElementCollection : Control.ControlCollection {
private Control container;
public ScrollablePanelElementCollection(Control owner, Panel container) : base(owner) { this.container = container; }
public override void Add(Control value) {
if (this.Count == 0)
base.Add(value);
else
container.Controls.Add(value);
}
}
}
namespace MagererPlayer {
public partial class FileExplorer : UserControl, IScrollable {
private string _directory;
public float visiblePercent {
get => scrollablePanel.visiblePercent;
set => scrollablePanel.visiblePercent = value;
}
public int scrollValue {
get => scrollablePanel.scrollValue;
set => scrollablePanel.scrollValue = value;
}
public CustomScrollBar scrollBar {
get => scrollablePanel.scrollBar;
set => scrollablePanel.scrollBar = value;
}
public string Directory {
get => this._directory;
set {
if (System.IO.Directory.Exists(value)) {
this._directory = value;
GetDirectory();
}
}
}
public float ScrollSpeedMultiplier { get; set; }
public EventHandler OnDirectoryChanged = null;
public EventHandler<FileSelectedEventArgs> OnFileSelected = null;
public FileExplorer() {
InitializeComponent();
}
private void FileExplorer_Load(object sender, EventArgs e) {
AdjustSize();
}
private void FileExplorer_Resize(object sender, EventArgs e) {
AdjustSize();
}
private void AdjustSize() {
}
private void GetDirectory() {
scrollablePanel.Clear();
System.Diagnostics.Trace.WriteLine(this.Directory);
int offset = System.IO.Directory.GetParent(this.Directory) != null ? 1 : 0;
int colCount = scrollablePanel.Width / 145;
string[] dirs = System.IO.Directory.GetDirectories(new string(this.Directory.Where(c => !char.IsControl(c)).ToArray()));
Array.Sort(dirs);
if (offset == 1) {
DirectoryInfo parent = System.IO.Directory.GetParent(this.Directory);
scrollablePanel.Controls.Add(GetExplorerItem(parent.FullName, "..", new Point(0, 0)));
}
for (int i = 0; i < dirs.Length; i++)
scrollablePanel.Controls.Add(GetExplorerItem(dirs[i], new DirectoryInfo(dirs[i]).Name, new Point(((i + offset) % colCount) * 145, (i + offset) / colCount * 115 + 10)));
offset += dirs.Length;
dirs = System.IO.Directory.GetFiles(new string(this.Directory.Where(c => !char.IsControl(c)).ToArray()), "*.mp4", SearchOption.TopDirectoryOnly);
Array.Sort(dirs);
for (int i = 0; i < dirs.Length; i++)
scrollablePanel.Controls.Add(GetExplorerItem(dirs[i], new FileInfo(dirs[i]).Name, new Point(((i + offset) % colCount) * 145, (i + offset) / colCount * 115 + 10)));
System.Diagnostics.Trace.WriteLine(scrollablePanel.Count);
System.Diagnostics.Trace.WriteLine(scrollablePanel.VisibleControls);
this.Invalidate();
}
private Panel GetExplorerItem(string path, string text, Point location) {
BunifuImageButton bunifuImageButton = new BunifuImageButton();
Label label = new Label();
Panel panel = new Panel();
if (System.IO.Directory.Exists(path)) {
bunifuImageButton.Image = Properties.Resources.folder;
bunifuImageButton.Click += delegate (object sender, EventArgs e) {
this.Directory = path;
};
} else if (System.IO.File.Exists(path)) {
bunifuImageButton.Image = Properties.Resources.video_1;
}
bunifuImageButton.ImageActive = null;
bunifuImageButton.Location = new System.Drawing.Point(10, 0);
bunifuImageButton.Name = "bunifuImageButton";
bunifuImageButton.Size = new System.Drawing.Size(125, 70);
bunifuImageButton.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
bunifuImageButton.TabStop = false;
bunifuImageButton.Zoom = 5;
bunifuImageButton.Cursor = Cursors.Hand;
label.Font = new System.Drawing.Font("Century Gothic", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
label.ForeColor = System.Drawing.Color.FromArgb(224, 224, 224);
label.Name = "label";
label.Dock = DockStyle.Bottom;
label.Size = new Size(145, 40);
label.Text = text;
label.TextAlign = ContentAlignment.MiddleCenter;
label.AutoEllipsis = true;
panel.Controls.Add(bunifuImageButton);
panel.Controls.Add(label);
panel.Location = location;
panel.Name = "panel";
panel.Size = new System.Drawing.Size(145, 110);
return panel;
}
private void textBoxUrl_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Enter) {
this.Directory = this.textBoxUrl.Text;
this.textBoxUrl.Text = this.Directory;
}
}
}
public class FileSelectedEventArgs : EventArgs {
public FileInfo File { get; }
public FileSelectedEventArgs(string path) {
File = new FileInfo(path);
}
public FileSelectedEventArgs(FileInfo info) {
File = info;
}
}
}
The Scrollable panel contains an inner panel that gets moved up and down. The FileExplorer contains a textbox for the URL, a button, and a scrollable panel.
My problem is that when I run the application everything works fine. When I go to the parent folder (IMPORTANT, NOT A SUBDIR) and reenter the folder all controls that should be there are invisible, but they were added to the inner panel's control collection.
This is the only folder dir where this error occurs: D:\Serien\Anime\Yu-Gi-Oh! Zexal
Output of System.Diagnostics from FileExplorer.GetDirectory:
D:\Serien\Anime\Yu-Gi-Oh! Zexal
3
True
D:\Serien\Anime
47
True
D:\Serien\Anime\Yu-Gi-Oh! Zexal
3
True
Screenshot of app start:
Screenshot after reenter:

Keep WPF Calendar open after date selection from DatePicker

I'm attempting to customise the WPF DatePicker and it's calendar popup component to keep the popup open after the user has selected a date. This is due to the extension to the style I have made that allows the user to select a time.
The calendar should only be closed once the user has clicked a 'finished' button in the style.
I've made multiple attempts at doing this, each with varying degrees of success but I have to admit, a fully working solution has eluded me.
Example solution using an override on IsDropDownOpen;
IsDropDownOpenProperty.OverrideMetadata(typeof(CustomDatePicker), new
FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
{
var instance = d as CustomDatePicker;
if (instance != null)
{
if (instance.CalendarPopup.IsOpen == false)
{
return true;
}
var coalesed = (bool) baseValue;
if (coalesed == false)
{
return instance.PopupStaysOpenAfterSelection;
}
}
return baseValue;
}
'PopupStaysOpenAfterSelection' is a dependency property I have created so that this behaviour is customizable.
Is an example of the popup staying open and closing when the user clicks 'done' however the popup will no longer reopen after interacting with the datepicker button.
Am I missing something obvious with this approach / is there a working solution?
EDIT:
ROOT CONTROL
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using Calendar = System.Windows.Controls.Calendar;
namespace CommonControls.Calendar
{
[TemplatePart(Name = NavigationPreviousButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationNextButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationTodayButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = NavigationDoneButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = DatePickerButtonPartName, Type = typeof(Button))]
[TemplatePart(Name = DatePickerTextBoxPartName, Type = typeof(DatePickerTextBox))]
[TemplatePart(Name = CalendarPopupPartName, Type = typeof(Popup))]
[TemplatePart(Name = DatePickerDisplayTextBoxPartName, Type = typeof(TextBlock))]
public class ExtendedDatePicker : DatePicker
{
public const string DatePickerDisplayTextBoxPartName = "PART_DisplayDateTextBox";
public const string DatePickerButtonPartName = "PART_Button";
public const string DatePickerTextBoxPartName = "PART_TextBox";
public const string CalendarPopupPartName = "PART_Popup";
public const string NavigationPreviousButtonPartName = "PART_NavPreviousButton";
public const string NavigationNextButtonPartName = "PART_NavNextButton";
public const string NavigationTodayButtonPartName = "PART_TodayButton";
public const string NavigationDoneButtonPartName = "PART_DoneButton";
public static readonly DependencyProperty BlackoutDatesInPastProperty =
DependencyProperty.Register("BlackoutDatesInPast", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(true, BlackoutDatesInPastPropertyChanged));
public static readonly DependencyProperty ThrowExceptionOnBlackoutDateSelectionProperty =
DependencyProperty.Register("ThrowExceptionOnBlackoutDateSelection", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false));
public static readonly DependencyProperty DatePickerButtonTemplateProperty =
DependencyProperty.Register("DatePickerButtonTemplate", typeof(ControlTemplate), typeof(ExtendedDatePicker), new PropertyMetadata(null));
public static readonly DependencyProperty CustomDateFormatProperty =
DependencyProperty.Register("CustomDateFormat", typeof(string), typeof(ExtendedDatePicker), new PropertyMetadata("d", CustomDateFormatPropertyChanged));
public static readonly DependencyProperty UseCustomDateFormatProperty =
DependencyProperty.Register("UseCustomDateFormat", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, UseCustomDateFormatPropertyChanged));
public static readonly DependencyProperty ShowTodayButtonProperty =
DependencyProperty.Register("ShowTodayButton", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, ShowTodayButtonPropertyChanged));
public static readonly DependencyProperty DaysToJumpProperty =
DependencyProperty.Register("DaysToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));
public static readonly DependencyProperty MonthsToJumpProperty =
DependencyProperty.Register("MonthsToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));
public static readonly DependencyProperty PopupStaysOpenAfterSelectionProperty =
DependencyProperty.Register("PopupStaysOpenAfterSelection", typeof(bool),
typeof(ExtendedDatePicker), new PropertyMetadata(null));
protected DatePickerTextBox DatePickerTextBox;
protected Calendar InternalCalendar;
protected Popup CalendarPopup;
protected bool PendingClose = false;
private Button _navigationPreviousButton;
private Button _navigationNextButton;
private Button _navigationTodayButton;
private Button _navigationDoneButton;
private Button _datePickerButton;
private TextBlock _displayDateTextBlock;
static ExtendedDatePicker()
{
IsDropDownOpenProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ExtendedOnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(typeof(ExtendedDatePicker)));
}
private static object OnCoerceIsDropDownOpen(DependencyObject d, object basevalue)
{
var instance = d as ExtendedDatePicker;
if (!instance.IsEnabled)
{
return false;
}
if (!instance.PopupStaysOpenAfterSelection)
{
return basevalue;
}
if (instance.PendingClose)
{
instance.PendingClose = false;
return false;
}
return true;
}
/**
* OLD
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
{
var instance = d as ExtendedDatePicker;
if (instance != null)
{
if (instance.IsDropDownOpen == false)
{
return true;
}
var coalesed = (bool) baseValue;
if (coalesed == false)
{
return instance.PopupStaysOpenAfterSelection;
}
}
return baseValue;
}
*/
private static void ExtendedOnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (ExtendedDatePicker) d;
instance.CoerceValue(IsDropDownOpenProperty);
}
public bool BlackoutDatesInPast
{
get { return (bool)GetValue(BlackoutDatesInPastProperty); }
set { SetValue(BlackoutDatesInPastProperty, value); }
}
public bool ThrowExceptionOnBlackoutDateSelection
{
get { return (bool)GetValue(ThrowExceptionOnBlackoutDateSelectionProperty); }
set { SetValue(ThrowExceptionOnBlackoutDateSelectionProperty, value); }
}
public ControlTemplate DatePickerButtonTemplate
{
get { return (ControlTemplate)GetValue(DatePickerButtonTemplateProperty); }
set { SetValue(DatePickerButtonTemplateProperty, value); }
}
public string CustomDateFormat
{
get { return (string)GetValue(CustomDateFormatProperty); }
set { SetValue(CustomDateFormatProperty, value); }
}
public bool UseCustomDateFormat
{
get { return (bool)GetValue(UseCustomDateFormatProperty); }
set { SetValue(UseCustomDateFormatProperty, value); }
}
public bool PopupStaysOpenAfterSelection
{
get { return (bool) GetValue(PopupStaysOpenAfterSelectionProperty); }
set { SetValue(PopupStaysOpenAfterSelectionProperty, value);}
}
public bool ShowTodayButton
{
get { return (bool)GetValue(ShowTodayButtonProperty); }
set { SetValue(ShowTodayButtonProperty, value); }
}
public int DaysToJump
{
get { return (int)GetValue(DaysToJumpProperty); }
set { SetValue(DaysToJumpProperty, value); }
}
public int MonthsToJump
{
get { return (int)GetValue(MonthsToJumpProperty); }
set { SetValue(MonthsToJumpProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_datePickerButton = GetTemplateChild(DatePickerButtonPartName) as Button;
_datePickerButton.CreateBinding(TemplateProperty, this, nameof(DatePickerButtonTemplate));
DatePickerTextBox = GetTemplateChild(DatePickerTextBoxPartName) as DatePickerTextBox;
_displayDateTextBlock = GetTemplateChild(DatePickerDisplayTextBoxPartName) as TextBlock;
CalendarPopup = GetTemplateChild(CalendarPopupPartName) as Popup;
InternalCalendar = CalendarPopup.Child as Calendar;
InternalCalendar.ApplyTemplate();
CalendarPopup.Loaded += CalendarPopup_Loaded;
_navigationPreviousButton = GetTemplateChild(NavigationPreviousButtonPartName) as Button;
_navigationNextButton = GetTemplateChild(NavigationNextButtonPartName) as Button;
_navigationTodayButton = InternalCalendar.Template.FindName(NavigationTodayButtonPartName, InternalCalendar) as Button;
_navigationDoneButton = InternalCalendar.Template.FindName(NavigationDoneButtonPartName, InternalCalendar) as Button;
if (_navigationTodayButton != null)
{
_navigationTodayButton.Click += TodayButton_Click;
}
if (_navigationPreviousButton != null)
{
_navigationPreviousButton.Click += NavPreviousButton_Click;
}
if (_navigationNextButton != null)
{
_navigationNextButton.Click += NavNextButton_Click;
}
if (_navigationDoneButton != null)
{
_navigationDoneButton.Click += NavDoneButton_Click;
}
RefreshBlackOutDates();
RefreshDisplayDate();
RefreshTextControlsVisibility();
RefreshTodayButtonVisibility();
}
private void CalendarPopup_Loaded(object sender, RoutedEventArgs e)
{
}
protected override void OnSelectedDateChanged(SelectionChangedEventArgs e)
{
base.OnSelectedDateChanged(e);
RefreshDisplayDate();
}
private static void BlackoutDatesInPastPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshBlackOutDates();
}
private static void CustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshDisplayDate();
}
private static void UseCustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshTextControlsVisibility();
source.RefreshDisplayDate();
}
private static void ShowTodayButtonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var source = (ExtendedDatePicker)d;
source.RefreshTodayButtonVisibility();
}
private void RefreshTextControlsVisibility()
{
if (DatePickerTextBox != null
&& _displayDateTextBlock != null)
{
DatePickerTextBox.Visibility = UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
_displayDateTextBlock.Visibility = !UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
}
}
private void RefreshTodayButtonVisibility()
{
if (_navigationTodayButton != null)
{
_navigationTodayButton.Visibility = ShowTodayButton ? Visibility.Visible : Visibility.Collapsed;
}
}
private void RefreshDisplayDate()
{
if (_displayDateTextBlock != null)
{
if (UseCustomDateFormat
&& !string.IsNullOrEmpty(CustomDateFormat))
{
if (SelectedDate.HasValue)
{
var d = SelectedDate.Value;
_displayDateTextBlock.Text = string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormat));
}
else
{
_displayDateTextBlock.Text = "Select a date";
}
}
}
}
private void RefreshBlackOutDates()
{
if (BlackoutDatesInPast)
{
BlackoutDates.AddDatesInPast();
}
}
private void GoToToday()
{
var newDate = DateTime.Today;
GoToDate(newDate);
}
private void GoToNextDate()
{
var selectedDate = SelectedDate;
if (selectedDate.HasValue)
{
var newDate = selectedDate.Value;
if (DaysToJump > 0)
{
newDate = newDate.AddDays(DaysToJump);
}
if (MonthsToJump > 0)
{
newDate = newDate.AddMonths(MonthsToJump);
}
GoToDate(newDate);
}
}
private void GoToPreviousDate()
{
var selectedDate = SelectedDate;
if (selectedDate.HasValue)
{
var newDate = selectedDate.Value;
if (DaysToJump > 0)
{
newDate = newDate.AddDays(DaysToJump * -1);
}
if (MonthsToJump > 0)
{
newDate = newDate.AddMonths(MonthsToJump * -1);
}
GoToDate(newDate);
}
}
private void GoToDate(DateTime date)
{
if (!BlackoutDates.Contains(date))
{
SelectedDate = date;
}
}
private void NavNextButton_Click(object sender, RoutedEventArgs e)
{
GoToNextDate();
}
private void NavPreviousButton_Click(object sender, RoutedEventArgs e)
{
GoToPreviousDate();
}
private void TodayButton_Click(object sender, RoutedEventArgs e)
{
GoToToday();
}
private void NavDoneButton_Click(object sender, RoutedEventArgs e)
{
IsDropDownOpen = false;
}
}
}
WITH TIME
public class CustomDatePickerWithTime : ExtendedDatePicker
{
public static readonly DependencyProperty MinuteIntervalProperty = DependencyProperty.Register(
"MinuteInterval", typeof(int), typeof(ExtendedDatePickerWithTime), new FrameworkPropertyMetadata(30));
public static readonly DependencyPropertyKey AvailableTimesPropertyKey = DependencyProperty.RegisterReadOnly(
"AvailableTimes", typeof(List<TimeSpan>), typeof(ExtendedDatePickerWithTime),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty AvailableTimesProperty = AvailableTimesPropertyKey.DependencyProperty;
public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
"SelectedTime", typeof(TimeSpan), typeof(ExtendedDatePickerWithTime), new PropertyMetadata(default(TimeSpan), OnSelectedTimePropertyChanged));
static ExtendedDatePickerWithTime()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePickerWithTime),
new FrameworkPropertyMetadata(typeof(ExtendedDatePickerWithTime)));
}
public int MinuteInterval
{
get { return (int) GetValue(MinuteIntervalProperty); }
set { SetValue(MinuteIntervalProperty, value);}
}
public List<TimeSpan> AvailableTimes
{
get { return (List<TimeSpan>) GetValue(AvailableTimesProperty); }
protected set { SetValue(AvailableTimesPropertyKey, value);}
}
public TimeSpan SelectedTime
{
get { return (TimeSpan) GetValue(SelectedTimeProperty); }
set { SetValue(SelectedTimeProperty, value);}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckIntervalConstraintsAndConstruct();
OverrideTextBoxEntryStyle();
}
private void OverrideTextBoxEntryStyle()
{
}
private static void OnSelectedTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (ExtendedDatePickerWithTime) d;
instance.SelectedDate = instance.SelectedDate?.Date.Add(instance.SelectedTime) ?? DateTime.Today.Add(instance.SelectedTime);
}
private void CheckIntervalConstraintsAndConstruct()
{
if (60 % MinuteInterval != 0)
{
throw new ArgumentOutOfRangeException(nameof(MinuteInterval), #"The supplied interval in minutes must be divisible by 60");
}
var times = new List<TimeSpan>();
var starting = DateTime.Today;
var ending = DateTime.Today.AddDays(1);
for (var ts = starting; ts <= ending.AddMinutes(MinuteInterval *-1); ts = ts.AddMinutes(MinuteInterval))
{
times.Add(ts.TimeOfDay);
}
AvailableTimes = times;
}
}
XAML: (length exceeds allowed body)
https://pastebin.com/W9KrtM8E

Creating a functional Double Control

I have a custom control called DoubleNumericBox that validates and accepts user input like 23,00, 0,9, 23.900,01, 34... etc.
The problem starts when I try to binding something to it. The binding is not reliable enough, some times the control won't display the new value, but if I set the DataContext one more time it will set the value, etc.
So, I must be doing something very wrong with my custom properties and events.
Custom Properties/Events
Value : Double
MinValue : Double
MaxValue : Double
ValueChanged : Event
Expected Behaviour
Validate typed keys: Numbers, Commas and Points (Decimal separator and digit grouping glyph). My culture uses Comma as decimal separator.
Validate the whole text if (return to the latest Value if number not valid):
Text pasted.
Lost Focus.
Validate Min/Max limit.
Accept binding from Text or Value, and validate the binding value.
Code
public class DoubleNumericBox : TextBox
{
Variables:
public readonly static DependencyProperty MinValueProperty;
public readonly static DependencyProperty ValueProperty;
public readonly static DependencyProperty MaxValueProperty;
Properties:
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetCurrentValue(MinValueProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set
{
SetCurrentValue(ValueProperty, value);
RaiseValueChangedEvent();
}
}
public double MaxValue
{
get { return (double)GetValue(MaxValueProperty); }
set { SetCurrentValue(MaxValueProperty, value); }
}
Event:
public static readonly RoutedEvent ValueChangedEvent;
public event RoutedEventHandler ValueChanged
{
//Provide CLR accessors for the event
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
public void RaiseValueChangedEvent()
{
var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
RaiseEvent(newEventArgs);
}
Constructor/Override:
static DoubleNumericBox()
{
MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));
ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
PreviewTextInput += DoubleNumericBox_PreviewTextInput;
ValueChanged += DoubleNumericBox_ValueChanged;
TextChanged += DoubleNumericBox_TextChanged;
LostFocus += DoubleNumericBox_LostFocus;
AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
}
Events:
private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as DoubleNumericBox;
if (textBox == null) return;
//textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);
textBox.RaiseValueChangedEvent();
}
private void DoubleNumericBox_ValueChanged(object sender, RoutedEventArgs e)
{
var textBox = sender as DoubleNumericBox;
if (textBox == null) return;
ValueChanged -= DoubleNumericBox_ValueChanged;
TextChanged -= DoubleNumericBox_TextChanged;
if (Value > MaxValue)
Value = MaxValue;
else if (Value < MinValue)
Value = MinValue;
textBox.Text = Text = String.Format("{0:###,###,##0.0###}", Value);
ValueChanged += DoubleNumericBox_ValueChanged;
TextChanged += DoubleNumericBox_TextChanged;
}
private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (String.IsNullOrEmpty(textBox.Text)) return;
if (IsTextDisallowed(textBox.Text)) return;
ValueChanged -= DoubleNumericBox_ValueChanged;
var newValue = Convert.ToDouble(textBox.Text);
if (newValue > MaxValue)
Value = MaxValue;
else if (newValue < MinValue)
Value = MinValue;
else
{
Value = newValue;
}
ValueChanged += DoubleNumericBox_ValueChanged;
}
private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (String.IsNullOrEmpty(e.Text))
{
e.Handled = true;
return;
}
//Only Numbers, comma and points.
if (IsEntryDisallowed(sender, e.Text))
{
e.Handled = true;
}
}
private void PastingEvent(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (IsTextDisallowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
{
TextChanged -= DoubleNumericBox_TextChanged;
Text = String.Format("{0:###,###,##0.0###}", Value);
TextChanged += DoubleNumericBox_TextChanged;
}
Methods:
private bool IsEntryDisallowed(object sender, string text)
{
var regex = new Regex(#"^[0-9]|\.|\,$");
if (regex.IsMatch(text))
{
return !CheckPontuation(sender, text);
}
//Not a number or a Comma/Point.
return true;
}
private bool IsTextDisallowed(string text)
{
var regex = new Regex(#"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
}
private bool CheckPontuation(object sender, string next)
{
var textBox = sender as TextBox;
if (textBox == null) return true;
if (Char.IsNumber(next.ToCharArray()[0]))
return true;
if (next.Equals("."))
{
var textAux = textBox.Text;
if (!String.IsNullOrEmpty(textBox.SelectedText))
textAux = textAux.Replace(textBox.SelectedText, "");
//Check if the user can add a point mark here.
var before = textAux.Substring(0, textBox.SelectionStart);
var after = textAux.Substring(textBox.SelectionStart);
//If no text, return true.
if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;
if (!String.IsNullOrEmpty(before))
{
if (before.Contains(',')) return false;
if (after.Contains("."))
{
var split = before.Split('.');
if (split.Last().Length != 3) return false;
}
}
if (!String.IsNullOrEmpty(after))
{
var split = after.Split('.', ',');
if (split.First().Length != 3) return false;
}
return true;
}
//Only one comma.
if (next.Equals(","))
{
return !textBox.Text.Any(x => x.Equals(','));
}
return true;
}
}
Can you guys help me out to make this custom control work better?
So a couple of gotchas I see in your code:
Do not use += / -= to hook up events in WPF controls, it can and will break routed events, use Addhandler / RemoveHandler instead.
I removed the unhooking and rehooking of events and used a member level flag instead for change loop issues. Here is the code I came up with, seem to bind fine to Value field.
A side note, you failed to account for multiple "." entry in your textbox so a user could type 345.34.434.23 which would not be prevented. I know to check this because I wrote a WPF FilterTextBox years ago and this came up in my testing.
public class DoubleNumericBox : TextBox
{
public readonly static DependencyProperty MinValueProperty;
public readonly static DependencyProperty ValueProperty;
public readonly static DependencyProperty MaxValueProperty;
public bool _bIgnoreChange = false;
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetCurrentValue(MinValueProperty, value); }
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set
{
SetCurrentValue(ValueProperty, value);
RaiseValueChangedEvent();
}
}
public double MaxValue
{
get { return (double)GetValue(MaxValueProperty); }
set { SetCurrentValue(MaxValueProperty, value); }
}
public static readonly RoutedEvent ValueChangedEvent;
public event RoutedEventHandler ValueChanged
{
//Provide CLR accessors for the event
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
public void RaiseValueChangedEvent()
{
var newEventArgs = new RoutedEventArgs(ValueChangedEvent);
RaiseEvent(newEventArgs);
}
static DoubleNumericBox()
{
MinValueProperty = DependencyProperty.Register("MinValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D));
ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(0D, ValueCallback));
MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(DoubleNumericBox), new FrameworkPropertyMetadata(Double.MaxValue));
ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DoubleNumericBox));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AddHandler(TextBox.PreviewTextInputEvent, new TextCompositionEventHandler(DoubleNumericBox_PreviewTextInput));
AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(DoubleNumericBox_TextChanged));
AddHandler(TextBox.LostFocusEvent, new RoutedEventHandler(DoubleNumericBox_LostFocus));
AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(PastingEvent));
}
private static void ValueCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as DoubleNumericBox;
if (textBox == null) return;
//textBox.Text = String.Format("{0:###,###,##0.0###}", textBox.Value);
textBox.DoubleNumericBox_ValueChanged();
}
private void DoubleNumericBox_ValueChanged()
{
if (Value > MaxValue)
Value = MaxValue;
else if (Value < MinValue)
Value = MinValue;
if (!_bIgnoreChange)
this.Text = Text = String.Format("{0:###,###,##0.0###}", Value);
}
private void DoubleNumericBox_TextChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (String.IsNullOrEmpty(textBox.Text)) return;
if (IsTextDisallowed(textBox.Text)) return;
//ValueChanged -= DoubleNumericBox_ValueChanged;
_bIgnoreChange = true;
Value = Convert.ToDouble(textBox.Text);
//if (newValue > MaxValue)
// Value = MaxValue;
//else if (newValue < MinValue)
// Value = MinValue;
//else
//{
// Value = newValue;
//}
_bIgnoreChange = false;
//ValueChanged += DoubleNumericBox_ValueChanged;
}
private void DoubleNumericBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (String.IsNullOrEmpty(e.Text))
{
e.Handled = true;
return;
}
//Only Numbers, comma and points.
if (IsEntryDisallowed(sender, e.Text))
{
e.Handled = true;
}
}
private void PastingEvent(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(String)))
{
var text = (String)e.DataObject.GetData(typeof(String));
if (IsTextDisallowed(text))
{
e.CancelCommand();
}
}
else
{
e.CancelCommand();
}
}
private void DoubleNumericBox_LostFocus(object sender, RoutedEventArgs e)
{
//TextChanged -= DoubleNumericBox_TextChanged;
Text = String.Format("{0:###,###,##0.0###}", Value);
//TextChanged += DoubleNumericBox_TextChanged;
}
private bool IsEntryDisallowed(object sender, string text)
{
var regex = new Regex(#"^[0-9]|\.|\,$");
if (regex.IsMatch(text))
{
return !CheckPontuation(sender, text);
}
//Not a number or a Comma/Point.
return true;
}
private bool IsTextDisallowed(string text)
{
var regex = new Regex(#"^((\d+)|(\d{1,3}(\.\d{3})+)|(\d{1,3}(\.\d{3})(\,\d{3})+))((\,\d{4})|(\,\d{3})|(\,\d{2})|(\,\d{1})|(\,))?$");
return !regex.IsMatch(text); //\d+(?:,\d{1,2})?
}
private bool CheckPontuation(object sender, string next)
{
var textBox = sender as TextBox;
if (textBox == null) return true;
if (Char.IsNumber(next.ToCharArray()[0]))
return true;
if (next.Equals("."))
{
var textAux = textBox.Text;
if (!String.IsNullOrEmpty(textBox.SelectedText))
textAux = textAux.Replace(textBox.SelectedText, "");
//Check if the user can add a point mark here.
var before = textAux.Substring(0, textBox.SelectionStart);
var after = textAux.Substring(textBox.SelectionStart);
//If no text, return true.
if (String.IsNullOrEmpty(before) && String.IsNullOrEmpty(after)) return true;
if (!String.IsNullOrEmpty(before))
{
if (before.Contains(',')) return false;
if (after.Contains("."))
{
var split = before.Split('.');
if (split.Last().Length != 3) return false;
}
}
if (!String.IsNullOrEmpty(after))
{
var split = after.Split('.', ',');
if (split.First().Length != 3) return false;
}
return true;
}
//Only one comma.
if (next.Equals(","))
{
return !textBox.Text.Any(x => x.Equals(','));
}
return true;
}
}

How to get a WPF datagrid column resize event?

I want to dynamically load and store the Datagrid column widths from my ini file. Write to my inifile for every resize of the column width. Which event can i ues for this. Could any body give any Suggetions or sample code for this.
i use apllicationsettings within a behavior for such things and save the information on application exit.
usage
<DataGrid>
<i:Interaction.Behaviors>
<local:DataGridBehavior GridSettings="{Binding Source={x:Static local:MySettings.Instance},Mode=OneWay}" />
</i:Interaction.Behaviors>
</DataGrid>
settings
[SettingsManageabilityAttribute(SettingsManageability.Roaming)]
public sealed class MySettings: ApplicationSettingsBase, IGridSettings
{
private static readonly Lazy<MySettings> LazyInstance = new Lazy<MySettings>(() => new KadiaSettings());
public static MySettingsInstance { get { return LazyInstance.Value; } }
[UserScopedSettingAttribute()]
[DefaultSettingValue("")]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public SerializableDictionary<string, int> GridDisplayIndexList
{
get { return (SerializableDictionary<string, int>)this["GridDisplayIndexList"]; }
set { this["GridDisplayIndexList"] = value; }
}
[UserScopedSettingAttribute()]
[DefaultSettingValue("")]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public SerializableDictionary<string, Visibility> GridColumnVisibilityList
{
get { return (SerializableDictionary<string, Visibility>)this["GridColumnVisibilityList"]; }
set { this["GridColumnVisibilityList"] = value; }
}
[UserScopedSettingAttribute()]
[DefaultSettingValue("")]
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public SerializableDictionary<string, double> GridColumnWidthList
{
get { return (SerializableDictionary<string, double>)this["GridColumnWidthList"]; }
set { this["GridColumnWidthList"] = value; }
}
private MySettings()
{
Application.Current.Exit += OnExit;
}
private void OnExit(object sender, ExitEventArgs e)
{
this.Save();
}
}
public interface IGridSettings: INotifyPropertyChanged
{
SerializableDictionary<string, int> GridDisplayIndexList { get; }
SerializableDictionary<string, Visibility> GridColumnVisibilityList { get; }
SerializableDictionary<string, double> GridColumnWidthList { get; }
}
[XmlRoot("Dictionary")]
public class SerializableDictionary<TKey, TValue>: Dictionary<TKey, TValue>, IXmlSerializable
{
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
var keySerializer = new XmlSerializer(typeof(TKey));
var valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
var key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
var value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
var keySerializer = new XmlSerializer(typeof(TKey));
var valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
#endregion
}
behavior
public class DataGridBehavior : Behavior<DataGrid>
{
public static readonly DependencyProperty GridSettingsProperty =
DependencyProperty.Register("GridSettings", typeof(IGridSettings), typeof(DataGridBehavior), null);
public IGridSettings GridSettings
{
get { return (IGridSettings)GetValue(GridSettingsProperty); }
set { SetValue(GridSettingsProperty, value); }
}
public DataGridICollectionViewSortMerkerBehavior()
{
Application.Current.Exit += CurrentExit;
}
private void CurrentExit(object sender, ExitEventArgs e)
{
SetSettings();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
AssociatedObject.Unloaded += AssociatedObjectUnloaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
}
private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
{
SetSettings();
}
void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
var settings = GridSettings;
var columns = AssociatedObject.Columns.ToList();
var colCount = columns.Count;
foreach (var column in columns)
{
var key = column.Header.ToString();
if (settings.GridDisplayIndexList.ContainsKey(key))
{
//manchmal wird -1 als index abgespeichert
var index = settings.GridDisplayIndexList[key];
if(index > 0 && index < colCount)
column.DisplayIndex = index;
}
if (settings.GridColumnVisibilityList.ContainsKey(key))
{
column.Visibility = settings.GridColumnVisibilityList[key];
}
if (settings.GridColumnWidthList.ContainsKey(key))
{
column.Width = new DataGridLength(settings.GridColumnWidthList[key]);
}
}
}
private void SetSettings()
{
var settings = GridSettings;
foreach (var column in AssociatedObject.Columns)
{
var key = column.Header.ToString();
var displayindex = column.DisplayIndex;
var visibility = column.Visibility;
var width = column.ActualWidth;
if (settings.GridDisplayIndexList.ContainsKey(key))
{
settings.GridDisplayIndexList[key] = displayindex;
}
else
{
settings.GridDisplayIndexList.Add(key, displayindex);
}
if (settings.GridColumnVisibilityList.ContainsKey(key))
{
settings.GridColumnVisibilityList[key] = visibility;
}
else
{
settings.GridColumnVisibilityList.Add(key, visibility);
}
if (settings.GridColumnWidthList.ContainsKey(key))
{
settings.GridColumnWidthList[key] = width;
}
else
{
settings.GridColumnWidthList.Add(key, width);
}
}
}
}

Polyline bound to ObservableList<Point> doesn't refresh

I have a class (let's call it externalClass) with a ObservableCollection<Point> channel1 inside. (the class itself does NOT implements INotify)
In the MainWindow I have a polyline binded to this externalClass.channel1 that uses a converter from ObservableCollection to PointCollection.
So from the C# I bind DataContext = externalClass; and in the XAML the polyline looks like:
<Polyline Points="{Binding channel1, Converter={StaticResource pointCollectionConverter}}" Stroke="#FF00E100" Name="line" />
I have a test function that works like that:
public void test()
{
ObservableCollection<Point> newone = new ObservableCollection<Point>();
for (int i = 0; i < debugCh1.Count; i++)
{
Point p1 = debugCh1[i];
p1.Y = p1.Y + 1;
newone.Add(p1);
}
channel1= newone;
}
If I add a breakpoint in the converter itself I can see that on start-up it is called (and actually the initial values (hard-coded) are displayed. But when I add the test function to a button .. it does nothing (the converter is not called)
Any idea idea of where the notification of the changes is being stopped ???
SOLUTION
After reading the answers and googleling a bit more I came out with the soulition. Id like to post it there for everybody else
So .. The so-called externalClass must inherit from INotifyPropertyChanged and it must implement NotifyPropertyChanged
So all and all it must be declared like that:
public class externalClass : INotifyPropertyChanged
{
....
// at some point you have your ObservableCollection<smth> as a property
public ObservableCollection<Point> channel1 { get; set; }
....
//at some point you implement NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string caller)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
....
//then whenever you need to fire the notification
[..]do something with channel1
NotifyPropertyChanged("channel1");
And that's all. If you add a proper binding (as the one I showed in my question) the whole set up is gonna work.. At least mine worked hehehe
Good luck! And thanks to the people that helped me !! :D
Polyline Points probably does not listen to INotifyCollectionChanged when bound. Try exposing Channel1 as a property and raise the INotifyPropertyChanged with "Channel1"
First, you need create your custom class PointCollection:
public class PointCollection : ObservableCollection<Point>
{
public PointCollection()
{
}
public PointCollection(IEnumerable<Point> points) : base(points)
{
}
}
Second, you need create custom Polyline:
public class PolylineDynamic : Shape
{
private Geometry _polylineGeometry = Geometry.Empty;
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register(nameof(Points), typeof(PointCollection), typeof(PolylineDynamic), new FrameworkPropertyMetadata(PointsChanged));
private static void PointsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((PolylineDynamic)d).PointsChanged(e);
}
private void PointsChanged(DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is PointCollection oldPoints)
oldPoints.CollectionChanged -= OnPointsChanged;
if (e.NewValue is PointCollection newPoints)
newPoints.CollectionChanged += OnPointsChanged;
UpdatePolyline();
}
public PointCollection Points
{
get => (PointCollection)GetValue(PointsProperty);
set => SetValue(PointsProperty, value);
}
public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register(nameof(FillRule), typeof(FillRule), typeof(PolylineDynamic), new FrameworkPropertyMetadata(FillRule.EvenOdd));
public FillRule FillRule
{
get => (FillRule)GetValue(FillRuleProperty);
set => SetValue(FillRuleProperty, value);
}
protected override Geometry DefiningGeometry => _polylineGeometry;
private void OnPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var aPoint = e.NewItems.OfType<Point>().Single();
Add(aPoint, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
Remove(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Replace:
var rPoint = e.NewItems.OfType<Point>().Single();
Replace(rPoint, e.NewStartingIndex);
break;
}
}
protected void UpdatePolyline()
{
if (Points == null || Points.Count < 2)
_polylineGeometry = Geometry.Empty;
else
{
var pGeometry = GetPathGeometry();
for (var i = 0; i < Points.Count; i++)
{
if (i == 0)
continue;
var startPoint = Points[i - 1];
var point = Points[i];
var figure = new PathFigure { StartPoint = startPoint };
figure.Segments.Add(new LineSegment(point, true));
pGeometry.Figures.Add(figure);
}
}
}
private void Add(Point point, int index)
{
var pGeometry = GetPathGeometry();
if (pGeometry.Figures.Count == 0)
{
UpdatePolyline();
return;
}
if (index == Points.Count - 1)
{
var segment = new LineSegment(point, true);
var figure = new PathFigure { StartPoint = Points[index - 1] };
figure.Segments.Add(segment);
pGeometry.Figures.Add(figure);
}
else if (index == 0)
{
var segment = new LineSegment(Points[1], true);
var figure = new PathFigure { StartPoint = point };
figure.Segments.Add(segment);
pGeometry.Figures.Insert(0, figure);
}
else
{
var leftFigure = new PathFigure { StartPoint = Points[index - 1] };
leftFigure.Segments.Add(new LineSegment(point, true));
var rightFigure = new PathFigure { StartPoint = point };
rightFigure.Segments.Add(new LineSegment(Points[index + 1], true));
pGeometry.Figures.Insert(index - 1, leftFigure);
pGeometry.Figures.Insert(index, rightFigure);
}
InvalidateVisual();
}
private void Remove(int index)
{
var pGeometry = GetPathGeometry();
if (!pGeometry.Figures.Any())
{
_polylineGeometry = Geometry.Empty;
InvalidateVisual();
return;
}
if (index == Points.Count - 1 || index == 0)
pGeometry.Figures.RemoveAt(index);
else
{
var leftFigure = pGeometry.Figures[index - 1];
var rightFigure = pGeometry.Figures[index];
pGeometry.Figures.RemoveAt(index - 1);
rightFigure.StartPoint = ((LineSegment)leftFigure.Segments.Single()).Point;
}
InvalidateVisual();
}
private void Replace(Point point, int index)
{
var pGeometry = GetPathGeometry();
if (index == 0)
pGeometry.Figures[0].StartPoint = point;
else if (index == Points.Count - 1)
ReplaceSegment(pGeometry.Figures[index - 1], point);
else
{
ReplaceSegment(pGeometry.Figures[index - 1], point);
pGeometry.Figures[index].StartPoint = point;
}
InvalidateVisual();
}
private void ReplaceSegment(PathFigure figure, Point point)
{
figure.Segments.Clear();
figure.Segments.Add(new LineSegment(point, true));
}
private PathGeometry GetPathGeometry()
{
if (_polylineGeometry is PathGeometry pathGeometry)
return pathGeometry;
else
{
pathGeometry = new PathGeometry { FillRule = FillRule };
_polylineGeometry = pathGeometry;
return pathGeometry;
}
}
}
If you need more functionality, you can add more)

Resources