INotifyPropertyChanged not working on Gauge (Live Charts) - c#

I'm trying to bind color of Gauge fill property to SolidColorBrush i created code behind. It works fine for progressbar but not for Gauge. The binding works but it doesn't update automatically as same as progressbar. Is that possible to update it while not having to re-plot this? Thank you
XAML:
<Grid Grid.Column="0">
<lvc:Gauge Grid.Row="2" Grid.Column="0" Margin="5"
From="0" To="{Binding Path=attaBitrateUP}" Value="{Binding Path=actBitrateUP}" GaugeActiveFill="{Binding Path=up, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
<Grid Grid.Row="2">
<StackPanel>
<TextBlock Text="Carrier count:" Height="20" VerticalAlignment="Bottom" Margin="10,0,0,5"/>
<ProgressBar Height="25" Margin="10,0,10,0" Value="{Binding Path=chartValuesUP}" Maximum="{Binding Path=chartValuesCount}" Foreground="{Binding Path=up}" Background="{Binding Path=down}"/>
</StackPanel>
</Grid>
Code Behind:
private SolidColorBrush _up = new SolidColorBrush(Colors.OrangeRed);
public SolidColorBrush up
{
get { return _up; }
set
{
_up = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

The property GaugeActiveFill has no property changed callback registered. Therefore, it won't detect the change when the data binding is invoked. The library is implemented quite sloppy, frankly said.
But we can fix it ourselves by extending the original Gauge class to register the missing property changed handler:
FixedGauge.cs
public class FixedGauge : Gauge
{
static FixedGauge()
{
GaugeActiveFillProperty.OverrideMetadata(typeof(FixedGauge), new FrameworkPropertyMetadata(default(Brush), OnGaugeActiveFillChanged));
}
private PieSlice PART_Pie { get; set; }
public FixedGauge()
{
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (TryFindVisualChildElementByName(this, null, out PieSlice pie))
{
this.PART_Pie = pie;
}
}
private static void OnGaugeActiveFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as FixedGauge).OnGaugeActiveFillChanged(e.OldValue as Brush, e.NewValue as Brush);
protected virtual void OnGaugeActiveFillChanged(Brush oldBrush, Brush newBrush)
{
if (this.PART_Pie == null)
{
return;
}
this.PART_Pie.Fill = newBrush;
}
public static bool TryFindVisualChildElementByName<TChild>(
DependencyObject parent,
string childElementName,
out TChild resultElement) where TChild : FrameworkElement
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild frameworkElement)
{
if (string.IsNullOrWhiteSpace(childElementName) || frameworkElement.Name.Equals(
childElementName,
StringComparison.OrdinalIgnoreCase))
{
resultElement = frameworkElement;
return true;
}
}
if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
{
return true;
}
}
return false;
}
}
Then use this new control in place of the original:
<local:FixedGauge Grid.Row="2" Grid.Column="0" Margin="5"
From="0" To="{Binding Path=attaBitrateUP}"
Value="{Binding Path=actBitrateUP}"
GaugeActiveFill="{Binding Path=up}" />

Related

How to update visibility at runtime in WPF

I am currently developing a hamburger style menu in WPF. In this menu, there are some categories that each have an icon. When the menu is collapsed you can still see those icons. When you expand the menu, there should appear text next to it. My idea was to just set their visibility to Visible as soon as the menu opens but I've had a lot of trouble realizing this. Right now I'm trying to change their visibility by binding them to a property.
XAML:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
C# CS Class:
using System.Windows;
using System.Windows.Controls;
namespace APP
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool menuOpen = false;
private int closedMenuWidth = 50;
private int openMenuWidth = 210;
private string textboxVisibility;
public string TextboxVisibility
{
get { return textboxVisibility; }
set { textboxVisibility = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.TextboxVisibility = "Hidden";
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
if (menuOpen)
{
menuGrid.Width = closedMenuWidth;
menuOpen = false;
this.TextboxVisibility = "Hidden";
}
else
{
menuGrid.Width = openMenuWidth;
menuOpen = true;
this.TextboxVisibility = "Visible";
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
}
}
When I change the value within the MainWindow function, it does have an effect on it when it first starts. But the other times I try to change it, which is at runtime, nothing happens. I have tried all sorts of things with booleans and binding the actual Visibility type but nothing worked.
You should implemente INotifyPropertyChanged on your MainWindow class like this:
public partial class MainWindow: Window,INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What OnPropertyChanged method does is, whenever the value is setted, it notifies the view and refreshes it.
This will solve the problem but isn't the right way to use MVVM.
The way you should do this is to change the visibility property of the TextBox instead of binding the visibility property to a value:
First you have to add a name to the TextBlock you want to hide:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock Name="textblock" x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
And then you change the visibility in the code
private void MenuButton_Click(object sender, RoutedEventArgs e) {
if (menuOpen) {
menuGrid.Width = closedMenuWidth;
menuOpen = false;
textblock.Visibility = System.Windows.Visibility.Hidden;
}
else {
menuGrid.Width = openMenuWidth;
menuOpen = true;
textblock.Visibility = System.Windows.Visibility.Visible;
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
If you want to implement MVVM the right way you have to create a ViewModel class and add it as Data Context to your view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
And then on you MainWindowViewModel is where you change the property:
public class MainWindowViewModel: INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

How do I dynamicaly update a child control within a form every second without changing the code behind the form?

I have a child control within a form which shows the strength of the WiFi signal As this application is to be used on a tablet whithin a moving vehicle round the site. I would like to have this control within every form of my WPF application and I would like it to refresh every second without having to change the code behind each form it is on.
It is possible to refresh this control using the parent form's code behind by calling signalQualityView.Refresh(); every second but would like this functionality to be implemented within the SignalQualityView usercontrol .
public partial class SignalQualityView : UserControl, INotifyPropertyChanged
{
// wifi signal indicator taken from from https://stackoverflow.com/questions/20085284/c-sharp-wpf-rating-control-similar-to-wifi-signal-indicator
private NetworkInformationService _networkInformationService;
public SignalQualityView()
{
InitializeComponent();
DataContext = this;
_networkInformationService = new NetworkInformationService();
Refresh();
}
public void Refresh()
{
Task.Run(() =>
{
WirelessNetwork wirelessNetwork = _networkInformationService.GetWirelessNetworkDetails();
var signalQuality = wirelessNetwork.SignalQuality;
if (signalQuality >= 80)
RatingValue = 5;
else if (signalQuality >= 60)
RatingValue = 4;
else if (signalQuality >= 40)
RatingValue = 3;
else if (signalQuality >= 20)
RatingValue = 2;
else if (signalQuality >= 1)
RatingValue = 1;
else
RatingValue = 0;
Task.Delay(1000);
Refresh();
});
}
public int RatingValue
{
get { return (int)GetValue(RatingValueProperty); }
set
{
SetValue(RatingValueProperty, value);
OnPropertyChanged();
}
}
// Using a DependencyProperty as the backing store for RatingValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RatingValueProperty =
DependencyProperty.Register("RatingValue", typeof(int), typeof(SignalQualityView), new UIPropertyMetadata(0));
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RatingConverter : IValueConverter
{
public Brush OnBrush { get; set; }
public Brush OffBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int rating = 0;
int number = 0;
if (int.TryParse(value.ToString(), out rating) && int.TryParse(parameter.ToString(), out number))
{
if (rating >= number)
{
return OnBrush;
}
return OffBrush;
}
return Brushes.Transparent;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
<UserControl x:Class="App.PlugIn.Controls.SignalQualityView"
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"
xmlns:local="clr-namespace:DPWorld.PlugIn.Controls"
mc:Ignorable="d"
Height="40" Width="60">
<Grid Background="black" >
<Grid.Resources>
<local:RatingConverter x:Key="RatingConverter" OnBrush="LightBlue" OffBrush="Black" />
<Style TargetType="Rectangle">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
</Grid.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Black" VerticalAlignment="Center">
<Rectangle Width="5" Height="5" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=1}"/>
<Rectangle Width="5" Height="10" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=2}"/>
<Rectangle Width="5" Height="15" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=3}"/>
<Rectangle Width="5" Height="20" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=4}"/>
<Rectangle Width="5" Height="25" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=5}"/>
</StackPanel>
<Label Content="SIGNAL" Foreground="LightBlue" VerticalAlignment="Top" Height="15" FontSize="8" Margin="10,0,18,0" Padding="0,0,0,0"/>
</Grid>
</UserControl>
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pi="clr-namespace:App.PlugIn.Controls;assembly=PlugIn"
Title="Window1" Height="300" Width="300">
<Grid>
<pi:SignalQualityView Name="signalQualityView" />
</Grid>
</Window>
The number of signal bars are meant highlighted depending on the signal strength but 0 bars are highlighted.
A simple UserControl that updates itself periodically would use a DispatcherTimer with a Tick event handler that updates a dependency property of the control. An element in the control's XAML would bind the control property by a RelativeSource Binding.
A simple digital clock as example:
<UserControl ...>
<Grid>
<TextBlock Text="{Binding Time,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</Grid>
</UserControl>
Code behind:
public partial class Clock : UserControl
{
public Clock()
{
InitializeComponent();
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(0.5) };
timer.Tick += (s, e) => Time = DateTime.Now.ToString("HH:mm:ss");
timer.Start();
}
public static readonly DependencyProperty TimeProperty =
DependencyProperty.Register(nameof(Time), typeof(string), typeof(Clock));
public string Time
{
get { return (string)GetValue(TimeProperty); }
set { SetValue(TimeProperty, value); }
}
}
When using WPF, data on a window that changes should come from an IObservable property of the bound object. When the property changes, the display value changes.
Put the current value into a property of a shared object using a task that runs every second. Share the object across all bound objects and no code behind is required.
You start of with a class that can be shared and observed:
public class WiFiStrength : IObservable<int>
{
private int _signalQuality;
private int _ratingValue;
private class Unsubscriber : IDisposable
{
private List<IObserver<int>> _observers;
private readonly IObserver<int> _observer;
public Unsubscriber(List<IObserver<int>> observers, IObserver<int> observer)
{
_observers = observers;
_observer = observer;
}
public void Dispose()
{
if (_observers != null)
{
_observers.Remove(_observer);
_observers = null;
}
}
}
private List<IObserver<int>> _observers = new List<IObserver<int>>();
private void SetAndRaiseIfChanged(int newRating)
{
if (_ratingValue != newRating)
{
_ratingValue = newRating;
foreach (var observer in _observers)
{
observer.OnNext(newRating);
}
}
}
private static WiFiStrength _sharedInstance;
private static Task _updater;
private static CancellationTokenSource cts;
private static void UpdateStrength(WiFiStrength model, CancellationToken ct)
{
var rnd = new Random();
while (!ct.IsCancellationRequested)
{
model.SignalQuality = rnd.Next(100);
Thread.Sleep(1000);
}
}
public static WiFiStrength SharedInstance()
{
if (_sharedInstance == null)
{
_sharedInstance = new WiFiStrength();
cts = new CancellationTokenSource();
_updater = new Task(() => UpdateStrength(_sharedInstance, cts.Token));
_updater.Start();
}
return _sharedInstance;
}
public int SignalQuality
{
get => _signalQuality;
set
{
_signalQuality = value;
if (_signalQuality >= 80)
{
SetAndRaiseIfChanged(5);
}
else if (_signalQuality >= 60)
{
SetAndRaiseIfChanged(4);
}
else if (_signalQuality >= 40)
{
SetAndRaiseIfChanged(3);
}
else if (_signalQuality >= 20)
{
SetAndRaiseIfChanged(2);
}
else if (_signalQuality >= 1)
{
SetAndRaiseIfChanged(1);
}
else
{
SetAndRaiseIfChanged(0);
}
}
}
public int SignalRating => _ratingValue;
public IDisposable Subscribe(IObserver<int> observer)
{
_observers.Add(observer);
return new Unsubscriber(_observers, observer);
}
}
You attach it to your bound class:
public class ViewModel1 : INotifyPropertyChanged
{
public string TextField { get; set; }
public WiFiStrength WiFi { get; set; }
private IObserver<int> _observer;
private class WiFiObserver : IObserver<int>
{
private readonly ViewModel1 _parent;
private readonly IDisposable _unsubscribe;
public WiFiObserver(ViewModel1 parent, WiFiStrength observed)
{
_parent = parent;
_unsubscribe = parent.WiFi.Subscribe(this);
}
public void OnNext(int value)
{
_parent.NotifyPropertyChanged("WiFi");
}
public void OnError(Exception error)
{
_unsubscribe.Dispose();
}
public void OnCompleted()
{
_unsubscribe.Dispose();
}
}
public ViewModel1()
{
WiFi = WiFiStrength.SharedInstance();
_observer = new WiFiObserver(this, WiFi);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Your class looks for changes, and notifies the owning form if it does change.
Your XAML need only bind to the property like this:
<Label Content="{Binding WiFi.SignalRating, Mode=OneWay}"/>
Use on as many forms as you like.

menuflyout choosed item passing to command mvvm

I'm trying to bind menuflyoutitem of choosen item in listview to Delete Command. Flyoutmenu shows when I'm holding element on list, so I can't bind it to SelectedItem property in viewmodel.
SelectedItem property works fine, but i have to tap element first and then hold item for showing menu and then delete. How can i pass sender from Holding to my property in viewmodel?
View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="My List App"
HorizontalAlignment="Center"
Style="{ThemeResource HeaderTextBlockStyle}" />
<ListView x:Name="myListView"
Grid.Row="1"
ItemsSource="{Binding AllMyLists}"
SelectedItem="{Binding SelectedList, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<controls:OpenMenuFlyoutAction />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Delete"
Command="{Binding ElementName=myListView, Path=DataContext.DeleteEntryListCommand}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<TextBlock Text="{Binding Name}"
Style="{ThemeResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
OpenMenuFlyoutAction for used for showing flyoutMenu:
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
return sender;
}
}
And My ViewModel:
public class AllListsPageViewModel : Microsoft.Practices.Prism.Mvvm.ViewModel, Interfaces.IAllListsPageViewModel
{
#region Fields
private ObservableCollection<EntryList> _allMyLists;
private EntryList _selectedList;
private DelegateCommand _addEntryListCommand;
private DelegateCommand _deleteEntryListCommand;
private readonly INavigationService _navigationService;
#endregion //Fields
#region Construction
public AllListsPageViewModel(INavigationService navigationService) { ... }
#endregion //Construction
#region Properties
public ObservableCollection<EntryList> AllMyLists
{
get { return _allMyLists; }
set { SetProperty(ref _allMyLists, value); }
}
public EntryList SelectedList
{
get { return _selectedList; }
set { SetProperty(ref _selectedList, value); }
}
#endregion //Properties
#region Methods
private void loadData() { }
private bool _canAddEntryList() { return true; }
private void _addEntryList() { ... }
private bool _canDeleteEntryList() { ... }
private void _deleteEntryList()
{
//How to get sender from holding event here?
_allMyLists.Remove(_selectedList);
}
#endregion //Methods
#region Commands
public ICommand AddEntryListCommand { ... }
public ICommand DeleteEntryListCommand
{
get
{
if (_deleteEntryListCommand == null)
{
_deleteEntryListCommand = new DelegateCommand(_deleteEntryList, _canDeleteEntryList);
}
return _deleteEntryListCommand;
}
}
#endregion //Commands
}
Thanks in advance.
I had the same problem today and I have resolved as follows:
namespace your.namespace
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
private static object holdedObject;
public object Execute(object sender, object parameter)
{
FrameworkElement senderElement = sender as FrameworkElement;
FlyoutBase flyoutBase = FlyoutBase.GetAttachedFlyout(senderElement);
flyoutBase.ShowAt(senderElement);
var eventArgs = parameter as HoldingRoutedEventArgs;
if (eventArgs == null)
{
return null;
}
var element = eventArgs.OriginalSource as FrameworkElement;
if (element != null)
{
HoldedObject = element.DataContext;
}
return null;
}
public static object HoldedObject
{
get { return holdedObject; }
set
{
holdedObject = value;
}
}
}
}
Then you can access the object as follows:
var foo = OpenMenuFlyoutAction.HoldedObject as Foo;
I think it's not bad solution that the HoldedObject is static as you can not do hold two items at the same time.

Click-to-Edit Control LostFocus event issue

I am working on a simple Custom Control that should go to Edit mode by double clicking on it
The concept is based on this question Click-to-edit in Silverlight
On a double click it changes initial template on Edit Template
and it seems to be pretty clear, except the part (5) How to change the template Back when the Control Looses the focus
The Lost Focus event is fired only when contained controls are loosing focus
Here is an article that talk about it
http://programmerpayback.com/2008/11/20/gotfocus-and-lostfocus-events-on-containers/
I have tried to implement same Technic but still no result, I cannot get LostFocus event working for me when I click outside of a control
Where is my issue?
My XAML
<ContentControl x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
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:obj="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:behaviour="clr-namespace:Splan_RiaBusinessApplication.Behavior"
xmlns:controls="clr-namespace:Splan_RiaBusinessApplication.Controls"
xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
IsTabStop="True"
IsEnabled="True"
Visibility="Visible"
d:DesignHeight="100" d:DesignWidth="200"
d:Height="200" d:Width="200"
>
<ContentControl.Resources>
<ControlTemplate x:Key="DisplayTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Target.Code, Mode=TwoWay}" />
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" >
<TextBlock Text="{Binding Target.Start, Mode=TwoWay, StringFormat=hh\\:mm }" />
<TextBlock Text='-' />
<TextBlock Text="{Binding Target.End, Mode=TwoWay, StringFormat=hh\\:mm }" />
</StackPanel>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="EditTemplate">
<Grid Background="Aqua" Height="200" Width="200">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Width="100" Height="25" x:Name="cbTimeCode"
ItemsSource="{Binding TimeCodes}"
SelectedValue="{Binding Target.CodeId, Mode=TwoWay}"
SelectedValuePath="TimeCodeId"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Target.Code}" />
<TextBlock Grid.Column="1" Text="{Binding Target.Description}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger>
<behaviour:ResolveElementName PropertyName="ItemsSource" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<!--<controls:TimeRangePickerControl Grid.Row="1" StartTime="{Binding Target.Start, Mode=TwoWay}" EndTime="{Binding Target.End, Mode=TwoWay}"/>-->
</Grid>
</ControlTemplate>
</ContentControl.Resources>
<Grid x:Name="Layout" Background="Aquamarine">
<ItemsControl x:Name="PlaceHolder" Template="{StaticResource DisplayTemplate}">
</ItemsControl>
</Grid>
</ContentControl>
Code Behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Splan_RiaBusinessApplication.Controls
{
public class TimeCode
{
public int TimeCodeId {get;set;}
public string Code { get; set; }
public string Description { get; set; }
}
public class TimeDetail
{
public int TimeDetailId { get;set; }
public int CodeId { get;set;}
public TimeSpan Start { get; set; }
public TimeSpan End { get; set; }
public string Code { get; set; }
public string Comment { get; set; }
}
public partial class TimeCodeControl : ContentControl
{
public class TimeCodeControlEventArgs : EventArgs
{
public string userName { get; set; }
}
private static TimeSpan DoubleClickThreshold = TimeSpan.FromMilliseconds(300);
private DateTime _lastClick;
private Boolean m_EditMode = false;
public Boolean EditMode
{
get { return m_EditMode; }
set
{
if (m_EditMode != value)
{
switch (value)
{
case false:
PlaceHolder.Template = this.Resources["DisplayTemplate"] as ControlTemplate;
break;
case true:
PlaceHolder.Template = this.Resources["EditTemplate"] as ControlTemplate;
break;
}
m_EditMode = value;
}
}
}
public bool IsFocused
{
get
{
return FocusManager.GetFocusedElement() == this;
}
}
public TimeCodeControl()
{
TimeCodes = new List<TimeCode>() { new TimeCode { TimeCodeId = 200, Code= "C", Description="Cash" } };
InitializeComponent();
Layout.DataContext = this;
this.IsTabStop = true;
this.Visibility = Visibility.Visible;
this.IsEnabled = true;
this.Focus();
Layout.MouseLeftButtonDown += Layout_MouseLeftButtonDown;
//Layout.KeyDown += Layout_KeyDown;
//Layout.KeyUp += Layout_KeyUp;
this.LostFocus += TimeCodeControl_LostFocus;
this.GotFocus += TimeCodeControl_GotFocus;
}
void TimeCodeControl_GotFocus(object sender, RoutedEventArgs e)
{
}
void TimeCodeControl_LostFocus(object sender, RoutedEventArgs e)
{
}
public TimeDetail Source
{
get { return (TimeDetail)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(TimeDetail), typeof(TimeCodeControl),
new PropertyMetadata(SourceChanged));
private static void SourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as TimeCodeControl;
if (control == null) return;
control.Target = control.Source; //.Copy();
}
public List<TimeCode> TimeCodes { get; set; }
public TimeDetail Target { get; set; }
private bool FocusIsInside(object parent)
{
bool rs = false;
dynamic oFocus = FocusManager.GetFocusedElement();
while (oFocus != null)
try
{
if ((oFocus.GetType() == parent.GetType()) && (oFocus.Equals(this)))
{
rs = true;
break;
}
else
{
oFocus = oFocus.Parent;
}
}
catch
{
break;
}
return rs;
}
private Boolean hasFocus = false;
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (!hasFocus)
{
hasFocus = true;
Debug.WriteLine("Container Got Focus");
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
//if (!FocusIsInside(Layout))
//{
// hasFocus = false;
// Debug.WriteLine("Container Lost Focus");
// EditMode = false;
//}
}
void Layout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (DateTime.Now - this._lastClick <= DoubleClickThreshold)
{
EditMode = true;
this._lastClick = DateTime.Now;
e.Handled = true;
return;
}
this._lastClick = DateTime.Now;
}
}
}
UPDATE :
I decided to utilize timers to identify a scenario when user brings a focus from outside of the container or just switches focus from one control to another inside of the container. it may be not the best solution but it seems to be working for now. I would appreciate any suggestions or recommendations on different approaches or implementations.
public partial class MyControl: ContentControl
{
...
public event EventHandler<RoutedEventArgs> LostFocus;
public event EventHandler<RoutedEventArgs> GotFocus;
bool Focused = false;
DispatcherTimer FocusTimer = null;
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (Focused) return;
Focused = true;
// it focused from the outside of the control
// becouse the timer wasn't initialised on the previous LostFocused event
// generated by other control in the same ContentControl contaner
if (FocusTimer == null)
{
if (GotFocus != null)
GotFocus(e.OriginalSource, e);
Debug.WriteLine("Got Focus ");
return;
}
// It was switched from one hosted control to another one
FocusTimer.Stop();
FocusTimer = null;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (e.OriginalSource is ComboBox && FocusManager.GetFocusedElement() is ComboBoxItem)
return;
FocusTimer = new DispatcherTimer();
Focused = false;
FocusTimer.Interval = new TimeSpan(0, 0, 0, 0, 50);
FocusTimer.Tick += (s, args) =>
{
FocusTimer.Stop();
FocusTimer = null;
// if after the timeout the focus still not triggered
// by another contained element
// the We lost a focus on the container
if (!Focused )
{
if(LostFocus != null)
LostFocus(e.OriginalSource, e);
Debug.WriteLine("Lost Focus " );
}
};
FocusTimer.Start();
}
...
}
There are several issues. Let's see...
Why are you not getting a LostFocus event when you click outside of the control?
Well, I fell victim to this false assumption some time ago too. The click outside does not change the focus unless you click a control that explicitly sets focus to itself on click (like a TextBox does, or the various Buttons).
Press Tab to navigate the keyboard focus to the next control and see if the event is raised.
But let's talk about the other issues:
ControlTemplate x:Key="DisplayTemplate" and ControlTemplate x:Key="EditTemplate"
Using ControlTemplates this way is not recommended. Rather use DataTemplate and corresponding ContentPresenters.
TimeCodeControl : ContentControl and x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
Yes I know that's possible, but not really useful. Let me explain:
You can write your own specialized Click-To-Edit Control as a one-shot tool: having a hardcoded DisplayTemplate and EditTemplate to edit TimeCode and TimeDetail data (basically what you did). But then you have no chance of ever using it and specifying another pair of Templates to allow editing of other data types.
So it doesn't make much sense to derive from ContentControl, you could as well derive from UserControl.
An alternative would be: Write your Click-To-Edit control as a general and reusable control, that offers two public properties: DisplayTemplate and EditTemplate. And don't make any assumptions about your DataContext. And again there is no benefit from having ContentControl as the parent class.
I recommend you derive from Control, add two DependencyProperties of type DataTemplate as mentioned earlier, define a default ControlTemplate with one or two ContentPresenters inside. In your control code you need to handle MouseLeftButtonDown and LostFocus and update a boolean flag accordingly.
Here is a working example:
...extension method to determine focus:
public static class ControlExtensions
{
public static bool IsFocused( this UIElement control )
{
DependencyObject parent;
for (DependencyObject potentialSubControl = FocusManager.GetFocusedElement() as DependencyObject; potentialSubControl != null; potentialSubControl = parent)
{
if (object.ReferenceEquals( potentialSubControl, control ))
{
return true;
}
parent = VisualTreeHelper.GetParent( potentialSubControl );
if (parent == null)
{
FrameworkElement element = potentialSubControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return false;
}
}
...and a nice custom control:
public class ClickToEditControl : Control
{
public ClickToEditControl()
{
DefaultStyleKey = typeof (ClickToEditControl);
MouseLeftButtonDown += OnMouseLeftButtonDown;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount==2)
{
GotoEditMode();
e.Handled = true;
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (!this.IsFocused())
GotoDisplayMode();
}
private void GotoDisplayMode()
{
IsInEditMode = false;
}
private void GotoEditMode()
{
IsInEditMode = true;
}
public DataTemplate EditTemplate
{
get { return (DataTemplate) GetValue( EditTemplateProperty ); }
set { SetValue( EditTemplateProperty, value ); }
}
public static readonly DependencyProperty EditTemplateProperty =
DependencyProperty.Register( "EditTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public DataTemplate DisplayTemplate
{
get { return (DataTemplate) GetValue( DisplayTemplateProperty ); }
set { SetValue( DisplayTemplateProperty, value ); }
}
public static readonly DependencyProperty DisplayTemplateProperty =
DependencyProperty.Register( "DisplayTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );
public bool IsInEditMode
{
get { return (bool) GetValue( IsInEditModeProperty ); }
set { SetValue( IsInEditModeProperty, value ); }
}
public static readonly DependencyProperty IsInEditModeProperty =
DependencyProperty.Register( "IsInEditMode", typeof( bool ), typeof( ClickToEditControl ), null );
}
...and ControlTemplate:
<clickToEdit:BoolToVisibilityConverter x:Key="VisibleIfInEditMode"/>
<clickToEdit:BoolToVisibilityConverter x:Key="CollapsedIfInEditMode" VisibleIfTrue="False"/>
<Style TargetType="clickToEdit:ClickToEditControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="clickToEdit:ClickToEditControl">
<Grid>
<ContentPresenter
ContentTemplate="{TemplateBinding EditTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource VisibleIfInEditMode}}"/>
<ContentPresenter
ContentTemplate="{TemplateBinding DisplayTemplate}"
Content="{Binding}"
Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource CollapsedIfInEditMode}}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
BoolToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter
{
public bool VisibleIfTrue { get; set; }
public BoolToVisibilityConverter(){VisibleIfTrue = true;}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (VisibleIfTrue)
return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
else
return ((bool) value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotSupportedException();}
}
Usage:
<clickToEdit:ClickToEditControl Height="20" Width="200">
<clickToEdit:ClickToEditControl.DisplayTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyText}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.DisplayTemplate>
<clickToEdit:ClickToEditControl.EditTemplate>
<DataTemplate>
<TextBox Text="{Binding MyText, Mode=TwoWay}"/>
</DataTemplate>
</clickToEdit:ClickToEditControl.EditTemplate>
</clickToEdit:ClickToEditControl>

Either adressing a property in a sub class or a binding issue

I have a problem and I'm not sure what it is. I have a class within a class that has a value that needs to be bound to a control, in this case visibility. The code is changing the value correctly but the output does not change (i.e collapse the control)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel Orientation="Vertical">
<Button x:Name="buttonOne" Content="Show Hide" Width="Auto" Click="buttonOne_Click"/>
<ListBox x:Name="aListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="blockOne" Grid.Column="0" Text="Raw "/>
<TextBlock x:Name="blockTwo" Grid.Column="1" Text="{Binding aValue}" Visibility="{Binding Path=visControl.VisibleState, BindsDirectlyToSource=True}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
public partial class MainPage : PhoneApplicationPage
{
private List<myClass> listOfClasses = new List<myClass>();
// Constructor
public MainPage()
{
myClass classA = new myClass("one");
myClass classB = new myClass("two");
myClass classC = new myClass("three");
listOfClasses.Add(classA);
listOfClasses.Add(classB);
listOfClasses.Add(classC);
InitializeComponent();
aListBox.ItemsSource = listOfClasses;
}
private void buttonOne_Click(object sender, RoutedEventArgs e)
{
foreach (myClass cl in listOfClasses)
if (cl.SwitchVisible)
cl.SwitchVisible = false;
else
cl.SwitchVisible = true;
}
}
public class myClass
{
private string _aValue;
private bool _switchVisible;
public bool SwitchVisible { get { return _switchVisible; } set { _switchVisible = value; visControl.changeVisibility(_switchVisible); } }
public string aValue { get { return _aValue; } }
public controlProperties visControl;
public myClass(string invalue)
{
visControl = new controlProperties();
visControl.VisibleState = Visibility.Visible;
_aValue = invalue;
}
}
public class controlProperties
{
private Visibility _visibility;
public Visibility VisibleState { get { return _visibility; } set { _visibility = value; } }
public void changeVisibility(bool isVisible)
{
if (isVisible)
_visibility = Visibility.Visible;
else
_visibility = Visibility.Collapsed;
}
}
Any ideas if this is a pathing issue or a binding problem?
If you want the control to be automatically updated when you change the value of the property, your class must implement the INotifyPropertyChanged interface.
For instance:
public class controlProperties : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Visibility _visibility;
public Visibility VisibleState
{
get
{
return _visibility;
}
set
{
_visibility = value;
this.NotifyPropertyChanged("VisibleState");
}
}
public void changeVisibility(bool isVisible)
{
if (isVisible)
this.VisibleState = Visibility.Visible;
else
this.VisibleState = Visibility.Collapsed;
}
private void NotifyPropertyChanged(string propertyName)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(sender, new PropertyChangedEventArgs(propertyName));
}
}
}

Categories