I have a list of heavy Controls, which I don't want to render before the user interacts with them (one at a time).
I want to show a placeholder for each Control until the placeholder is clicked (preferably focused) and then render the real Control.
What I've tried looks like this:
<ContentControl x:Name="theControl">
<TextBox x:Name="TextBlock" Text="Placeholder right here."/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused, ElementName=TextBlock}" Value="True">
<Setter Property="Content" >
<Setter.Value>
<Grid x:Name="theGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="CodeColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock>Heavy control part1</TextBlock>
<TextBlock Grid.Column="1">heavy control part2</TextBlock>
</Grid>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Anyone knows a better approach or what I'm missing?
I don't know if this is a better solution, but you can create the heavy control in code and then remove/add children after a GotFocus event.
Add a GotFocus event to your TextBlock and put the TextBlock in a Grid
<Grid Name="myGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<TextBox x:Name="TextBlock" Grid.Column="0" Grid.Row="0" Text="Placeholder right here." GotFocus="TextBlock_GotFocus" />
</Grid>
Then in the cs file
private void TextBlock_GotFocus(object sender, RoutedEventArgs e)
{
createNewControl();
}
private void createNewControl()
{
Grid myOtherGrid = new Grid();
RowDefinition newRow1 = new RowDefinition();
newRow1.Height = new GridLength(100.0);
RowDefinition newRow2 = new RowDefinition();
newRow2.Height = new GridLength(100.0);
ColumnDefinition newColumn1 = new ColumnDefinition();
newColumn1.Width = new GridLength(50.0);
ColumnDefinition newColumn2 = new ColumnDefinition();
newColumn2.Width = new GridLength(50.0);
myOtherGrid.RowDefinitions.Add(newRow1);
myOtherGrid.RowDefinitions.Add(newRow2);
myOtherGrid.ColumnDefinitions.Add(newColumn1);
myOtherGrid.ColumnDefinitions.Add(newColumn2);
TextBox myOtherTextBlock1 = new TextBox();
myOtherTextBlock1.Text = "new block 1";
TextBox myOtherTextBlock2 = new TextBox();
myOtherTextBlock2.Text = "new block 1";
myOtherGrid.Children.Add(myOtherTextBlock1);
Grid.SetRow(myOtherTextBlock1, 0);
Grid.SetColumn(myOtherTextBlock1, 0);
myOtherGrid.Children.Add(myOtherTextBlock2);
Grid.SetRow(myOtherTextBlock2, 1);
Grid.SetColumn(myOtherTextBlock2, 1);
myGrid.Children.Remove(TextBlock);
myGrid.Children.Add(myOtherGrid);
}
This is the general idea of what I managed to get working.
public partial class PlaceHolder : UserControl
{
private bool m_isReadOnly;
private object m_PlaceholdeContent;
private bool m_hasValue;
private object m_realContent;
public PlaceHolder()
{
InitializeComponent();
GotFocus += OnGotFocus;
}
private void OnGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
GotFocus -= OnGotFocus;
if (!RealContentIsUsed)
{
RealContentIsUsed = true;
Content = RealContent;
}
}
private bool RealContentIsUsed { get; set; }
public object RealContent
{
get { return m_realContent; }
set
{
m_realContent = value;
if (IsReadOnly || HasValue)
{
Content = m_realContent;
}
}
}
public object PlaceholdeContent
{
get { return m_PlaceholdeContent; }
set
{
m_PlaceholdeContent = value;
if (!RealContentIsUsed)
{
Content = m_PlaceholdeContent;
}
}
}
public bool IsReadOnly
{
get { return m_isReadOnly; }
set
{
m_isReadOnly = value;
if (value && !RealContentIsUsed)
{
Content = RealContent;
RealContentIsUsed = true;
}
}
}
public bool HasValue
{
get { return m_hasValue; }
set
{
m_hasValue = value;
if (HasValue && RealContentIsUsed == false)
{
Content = RealContent;
RealContentIsUsed = true;
}
}
}
}
Related
I am trying to take the text from clipboard, split it into an array and create a new row in the datagrid for each element in the array. However, I am having difficulty getting the method to trigger. Like so:
void delivGrid_Keydown(object sender, KeyEventArgs e)
{
if (e.Key == Key.V &&
(Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
string clipData = System.Windows.Clipboard.GetText();
string[] clipRows = Regex.Split(clipData, #"\r");
foreach (var row in clipRows)
{
delivGrid.Items.Add(new Deliverable { name = String.Empty, desc = row, rDays = String.Empty });
}
//viewModel.Paste()
}
}
I haven't been able to find anything on a paste event for a WPF Datagrid. Does anybody know of a way that I could trigger this event for to intercept the pasting?
You could check DataObject.AddPastingHandler which allow you to define a handler to the pasting event like shown here for a TextBox.
The problem is that the pasting event only happen when it the control already accepts it:
For a DataGrid, it will only happen when you attempt to paste an editable cell which is not what you are trying to achieve.
I think you should create a CustomControl inheriting from a control accepting paste directly (a TextBox for example), but you will have to recreate some of the dependency of a DataGrid (for the itemsSource for exemple).
I set up a small demo so that you can check the different behaviors:
MainWindow.xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--Simple DataGrid-->
<DataGrid x:Name="DataGrid" AutoGenerateColumns="False" MaxWidth="150" Grid.Column="0">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Col1" Binding="{Binding Path=BusinessItem1}">
</DataGridTextColumn>
<DataGridTextColumn x:Name="Col2" Binding="{Binding Path=BusinessItem2}"/>
</DataGrid.Columns>
</DataGrid>
<!--TextBox (so that we can use the copy paste functionnality) modified to show a dataGrid-->
<TextBox x:Name="TextBoxButActuallyReallyADataGrid" Grid.Column="1">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<DataGrid MinHeight="250" Margin="10" Background="LightBlue" MinWidth="250" x:Name="DataGridForDemoCustomControl" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="CustomCol1" Binding="{Binding Path=BusinessItem1}"/>
<DataGridTextColumn x:Name="CusomCol2" Binding="{Binding Path=BusinessItem2}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
<!--To show the default behavior on a real textBox-->
<TextBox x:Name="TestTexBox" Text="Default_Text" Grid.Column="2"/>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Since it is a demo, I didn't create a ViewModel nor the Bindings but I recommand doing it
this.GetData();
this.DataGrid.ItemsSource = this.MyData;
//Add an handler to the pasting event for the specified dataObject
//https://learn.microsoft.com/en-gb/dotnet/api/system.windows.dataobject.addpastinghandler?view=netcore-3.1
DataObject.AddPastingHandler(this.DataGrid, PasteHandler);
DataObject.AddPastingHandler(TextBoxButActuallyReallyADataGrid, PasteHandlerForTemplatedTextBox);
DataObject.AddPastingHandler(TestTexBox, PasteHandlerForTextBox);
}
private void PasteHandlerForTextBox(object sender, DataObjectPastingEventArgs e)
{
if (sender is TextBox textbox && e.DataObject.GetDataPresent(typeof(string)))
{
}
else
{
e.CancelCommand();
}
}
private void PasteHandlerForTemplatedTextBox(object sender, DataObjectPastingEventArgs e)
{
if (sender is TextBox dataGrid && e.DataObject.GetDataPresent(typeof(string)))
{
}
else
{
e.CancelCommand();
}
}
private void PasteHandler(object sender, DataObjectPastingEventArgs e)
{
if (sender is DataGrid dataGrid && e.DataObject.GetDataPresent(typeof(string)))
{
}
else
{
e.CancelCommand();
}
}
private void GetData()
{
for (int i = 0; i < 18; i++)
{
MyData.Add(new MyRowElement { BusinessItem1 = "text1_" + i.ToString(), BusinessItem2 = "text2_" + i.ToString() });
}
}
public ObservableCollection<MyRowElement> MyData { get; private set; } = new ObservableCollection<MyRowElement>();
}
public class MyRowElement
{
public string BusinessItem1 { get; set; }
public string BusinessItem2 { get; set; }
}
If you don't want to create a CustomControl you could do it like in my exemple but you won't be able to access the dataGrid by name in codeBehind.
You should be able to handle the PreviewKeyDown something like this:
delivGrid.PreviewKeyDown += (ss, ee) =>
{
if (ee.Key == Key.V && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.R)))
{
//...
//escape default behaviour:
ee.Handled = true;
}
};
what I am trying to do is somewhat out there, and I have yet to really see an example of this.
I am trying to validate a textbox entry that is essentially a required field (it cannot be null or empty). However, I do not have any access to the code behind, only to the XAML and data binding for the form.
From searching for a couple of days, I found out this cannot be done strictly in XAML (which would have been preferred), and had to create my own resource library to check for this. That is what I have done, but failed to get it to work.
Is this even a possibility? Or what would I have to do to get this to work?
What I have done so far was create a usercontrol template of a textbox to then use in the XAML (residing in an outside library):
<UserControl.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<DockPanel x:Name="dpMain" LastChildFill="True">
<Label/>
</DockPanel>
</Grid>
And the code behind:
namespace ClassLibrary.CustomControls
{
public partial class CssTextBox : UserControl
{
private TextBox _textbox = null;
private ObservableCollection<ValidationRule> _validationRules = null;
public CssTextBox()
{
InitializeComponent();
CreateControls();
ValidationRules = new ObservableCollection<ValidationRule>();
this.DataContextChanged += new DependencyPropertyChangedEventHandler(CssTextBoxDataChanged);
}
public ObservableCollection<ValidationRule> ValidationRules
{
get { return _validationRules; }
set { _validationRules = value; }
}
private void CreateControls()
{
_textbox = new TextBox() { Width = 100, Height = 20 };
_textbox.LostFocus += CssTextBoxLostFocus;
_textbox.Style = TextBoxErrorStyle;
}
public void CssTextBoxDataChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (_textbox != null)
{
var binding = new Binding();
binding.Source = this.DataContext;
binding.ValidatesOnDataErrors = true;
binding.ValidatesOnExceptions = true;
foreach (var rule in ValidationRules)
{
binding.ValidationRules.Add(rule);
}
binding.Path = new PropertyPath(BoundPropertyName);
_textbox.SetBinding(TextBox.TextProperty, binding);
dpMain.Children.Add(_textbox);
}
}
public void CssTextBoxLostFocus(object sender, RoutedEventArgs e)
{
var bindingExpression = _textbox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
private Style TextBoxErrorStyle
{
get
{
return (Style)FindResource("TextBoxStyle");
}
}
public string TextBoxErrorStyleName { get; set; }
public string BoundPropertyName { get; set; }
public string ValidationExpression { get; set; }
public string Text
{
get
{
return _textbox.Text;
}
}
public string ErrorText { get; set; }
}
And how it is being used (currently being tested in a WPF Sandbox project and only being referenced via XAML):
xmlns:css="clr-namespace:WpfSandbox.CustomControls" <!--Reference to library that holds above--!>
<css:CssTextBox TextBoxErrorStyleName="TextBoxStyle" Grid.Column="0" Grid.Row="1" Width="100" Height="20" VerticalAlignment="Top" >
<css:CssTextBox.ValidationRules>
<validators:NotNullOrEmptyValidationRule ErrorMessage="Cannot be Empty!" />
</css:CssTextBox.ValidationRules>
</css:CssTextBox>
<TextBox Grid.Column="0" Grid.Row="2" Width="auto" Height="20" VerticalAlignment="Top" Background="White" IsEnabled="True"/>
My issue with what I have now, is that it shows the textbox in my designer window in my sandbox application, but I cannot click into it when I run. It's almost like it does not exist.
Thanks for any insight!
You should read about WPF Data validation.
This link will help you:
https://msdn.microsoft.com/fr-fr/library/system.componentmodel.idataerrorinfo(v=vs.95).aspx
Is it possible to create a popup loading dialog in a Windows Universal app, which is similar to an Android progress dialog?
I have a section of my app that might take a bit of time to complete, so I'd like to show the user that the app is actually working. I want to do this by popping up a dialog overlay of some kind that shadows the background to stop user interaction.
Android Progress Dialog for comparison:
You can create a UserControl, with background color black and opacity arround 0.6
Then, put ProgressRing inside, create a DependencyProperty to control the activation of the ring from outside of the UserControl. Put the UserControl in XAML or create it at run time and inject into the Page.
UWP Progress Dialog
<UserControl
x:Class="HelperUWP.Controls.BusyIndicator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HelperUWP.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<Style x:Key="LockScreenTextStyle" BasedOn="{StaticResource BaseTextBlockStyle}" TargetType="TextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Margin" Value="15,0,0,0"/>
<Setter Property="TextAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid Background="Black" Opacity="0.4"/>
<Grid x:Name="gridBackground" HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent" Width="470" Height="120">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Margin="15,5,15,5" Orientation="Vertical" HorizontalAlignment="Center">
<ProgressRing x:Name="progressRing" Height="40" Width="40" HorizontalAlignment="Center" IsActive="True"
/>
<TextBlock HorizontalAlignment="Center" Foreground="White" x:Name="TitleTextBlock"
Style="{StaticResource LockScreenTextStyle}" Text="Performing Operation"/>
</StackPanel>
</Grid>
</Grid>
//cs
public sealed partial class BusyIndicator : UserControl
{
private Popup ParentPopup = null;
public BusyIndicator(string title = "Loading...", SolidColorBrush backgroundColor = null, SolidColorBrush foregroundColor = null)
{
this.InitializeComponent();
TitleTextBlock.Text = title;
this.Text = title;
if (backgroundColor != null)
this.gridBackground.Background = backgroundColor;
if (foregroundColor != null)
{
this.progressRing.Foreground = foregroundColor;
this.TitleTextBlock.Foreground = foregroundColor;
}
}
static SolidColorBrush def = Application.Current.Resources["SystemControlBackgroundAccentBrush"] as SolidColorBrush;
#region Public Methods
/// <summary>
/// Closes the BusyIndicator.
/// </summary>
public void Close()
{
// Close the parent; closes the dialog too.
// ((Popup)Parent).IsOpen = false;
if(this.ParentPopup!= null && this.ParentPopup.IsOpen == true)
this.ParentPopup.IsOpen = false;
}
#endregion Public Methods
#region Public Static Methods
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(500) };
/// <summary>
/// Locks the screen ans starts the BusyIndicator by creating a popup.
/// </summary>
/// <param name="title">The title to be displayed by the BusyIndicator.</param>
/// <returns>The BusyIndicator.</returns>
public void Start()
{
// Create a popup with the size of the app's window.
Popup popup = new Popup()
{
Height = Window.Current.Bounds.Height,
IsLightDismissEnabled = false,
Width = Window.Current.Bounds.Width
};
// Create the BusyIndicator as a child, having the same size as the app.
BusyIndicator busyIndicator = new BusyIndicator()
{
Height = popup.Height,
Width = popup.Width,
Text = this.Text,
};
this.timer.Start();
this.timer.Tick += (f, y) =>
{
busyIndicator.Text = Text;
};
// Set the child of the popop
popup.Child = busyIndicator;
// Postion the popup to the upper left corner
popup.SetValue(Canvas.LeftProperty, 0);
popup.SetValue(Canvas.TopProperty, 0);
// Open it.
this.ParentPopup = popup;
popup.IsOpen = true;
// Return the BusyIndicator
// return (busyIndicator);
}
//string
public static readonly DependencyProperty SetMessageProperty = DependencyProperty.Register("Text", typeof(string),
typeof(BusyIndicator), new PropertyMetadata("Loading...", new PropertyChangedCallback(OnMessageTextChanged)));
public string Text
{
get { return (string)GetValue(SetMessageProperty); }
set { SetValue(SetMessageProperty, value); }
}
private static void OnMessageTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BusyIndicator borderControl = d as BusyIndicator;
borderControl.OnMessageTextChanged(e);
}
private void OnMessageTextChanged(DependencyPropertyChangedEventArgs e)
{
this.TitleTextBlock.Text = e.NewValue.ToString();
}
//string
public static readonly DependencyProperty SetColorProperty = DependencyProperty.Register("SetColor", typeof(SolidColorBrush),
typeof(BusyIndicator), new PropertyMetadata(Colors.Green, new PropertyChangedCallback(OnColorTextChanged)));
public SolidColorBrush SetColor
{
get { return (SolidColorBrush)GetValue(SetColorProperty); }
set { SetValue(SetColorProperty, value); }
}
private static void OnColorTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BusyIndicator borderControl = d as BusyIndicator;
borderControl.OnColorTextChanged(e);
}
private void OnColorTextChanged(DependencyPropertyChangedEventArgs e)
{
this.TitleTextBlock.Foreground =(SolidColorBrush) e.NewValue;
this.progressRing.Foreground = (SolidColorBrush)e.NewValue;
}
}
//
Result
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>
I am using WPF Toolkit for charting. I am using a LineSeries for displaying the data change per second. Currently, I am able to update the graph as new points are added. But the X-Axis scale is fixed from 0 to 60 automatically. What I want is, after the first cycle, instead of the data plot showing from the starting of the axis, I want the X-Axis to shift by one division, like it is in an ECG display.
I managed to find the answer on my own. Please give suggestions fro improving the answer.
namespace WpfChartExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<ChartData> chartData;
ChartData objChartData;
Thread MyThread;
public MainWindow()
{
InitializeComponent();
chartData = new ObservableCollection<ChartData>();
DateTime dtnow = DateTime.Now;
objChartData = new ChartData() { Name = dtnow, Value = 0.0 };
chartData.Add(objChartData);
chartData.Add(new ChartData() { Name = (dtnow + TimeSpan.FromSeconds(2)), Value = new Random().NextDouble() * 100 });
chartData.Add(new ChartData() { Name = (dtnow + TimeSpan.FromSeconds(4)), Value = new Random().NextDouble() * 100 });
xAxis.Minimum = chartData[0].Name;
simChart.DataContext = chartData;
MyThread = new Thread(new ThreadStart(StartChartDataSimulation));
}
public void StartChartDataSimulation()
{
int i = 0;
while (true)
{
Dispatcher.Invoke(new Action(() =>
{
var data = new ChartData() { Name = DateTime.Now, Value = new Random().NextDouble() * 100 };
chartData.Add(data);
if (chartData.Count % 40 == 0 && i == 0)
{
xAxis.Minimum = chartData[i + 1].Name;
i++;
}
if (i >= 1)
{
xAxis.Minimum = chartData[i + 1].Name;
i++;
}
}));
Thread.Sleep(1000);
}
}
private void btnStartStop_Click(object sender, RoutedEventArgs e)
{
if ((string)btnStartStop.Content == "Start Simulation")
{
if (MyThread.ThreadState == ThreadState.Unstarted)
{
MyThread.Start();
}
else if (MyThread.ThreadState == ThreadState.Suspended)
{
MyThread.Resume();
}
btnStartStop.Content = "Stop Simulation";
}
else
{
MyThread.Suspend();
btnStartStop.Content = "Start Simulation";
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (MyThread.ThreadState == ThreadState.Running ||MyThread.ThreadState == ThreadState.WaitSleepJoin)
{
MyThread.Suspend();
}
Application.Current.Shutdown();
}
}
public class ChartData : INotifyPropertyChanged
{
DateTime _Name;
double _Value;
#region properties
public DateTime Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public double Value
{
get
{
return _Value;
}
set
{
_Value = value;
OnPropertyChanged("Value");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window x:Class="WpfChartExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chrt="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
Title="MainWindow" Height="350" Width="525" Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<chrt:Chart x:Name="simChart" Title="Simulation" Background="Beige">
<chrt:LineSeries IndependentValueBinding="{Binding Name}"
DependentValueBinding="{Binding Value}"
ItemsSource="{Binding}"
Background="DarkGray">
<chrt:LineSeries.DataPointStyle>
<Style TargetType="{x:Type chrt:LineDataPoint}">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Width" Value="5" />
<Setter Property="Height" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chrt:LineDataPoint">
<Grid x:Name="Root" Opacity="1">
<ToolTipService.ToolTip>
<StackPanel Margin="2,2,2,2">
<ContentControl Content="{TemplateBinding IndependentValue}"
ContentStringFormat="X-Value: {0:HH:mm:ss}"/>
<ContentControl Content="{TemplateBinding DependentValue}"
ContentStringFormat="Y-Value: {0:###.###}"/>
</StackPanel>
</ToolTipService.ToolTip>
<Ellipse StrokeThickness="{TemplateBinding BorderThickness}"
Stroke="{TemplateBinding BorderBrush}"
Fill="{TemplateBinding Background}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</chrt:LineSeries.DataPointStyle>
<chrt:LineSeries.IndependentAxis>
<chrt:DateTimeAxis Name="xAxis" ShowGridLines="True" Orientation="X">
<chrt:DateTimeAxis.AxisLabelStyle>
<Style TargetType="chrt:DateTimeAxisLabel">
<Setter Property="StringFormat" Value="{}{0:mm:ss}" />
</Style>
</chrt:DateTimeAxis.AxisLabelStyle>
</chrt:DateTimeAxis>
</chrt:LineSeries.IndependentAxis>
<chrt:LineSeries.DependentRangeAxis>
<chrt:LinearAxis Orientation="Y" ShowGridLines="True" Minimum="-50" Maximum="50"></chrt:LinearAxis>
</chrt:LineSeries.DependentRangeAxis>
<chrt:LineSeries.Title>
<TextBlock TextAlignment="Center">Time<LineBreak/>vs.<LineBreak/>Random<LineBreak/>Data</TextBlock>
</chrt:LineSeries.Title>
</chrt:LineSeries>
</chrt:Chart>
<Button Name="btnStartStop" Width="Auto" Height="30" Grid.Row="1" HorizontalAlignment="Right" Margin="10" Click="btnStartStop_Click">Start Simulation</Button>
</Grid>