I have a AutoCompleteBox as a DataGrid column type. Like so:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
However, I want to restrict the user's input to uppercase. On TextBoxes I can do so like the following, but I can't get that to work with the AutoCompleteBoxes.
<DataGridTextColumn Binding="{Binding UpdateSourceTrigger=PropertyChanged, Path=Thing}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="CharacterCasing" Value="Upper" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
I've tried this:
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}"
TextChanged="AutoComplete_TextChanged" />
With this:
private void AutoComplete_TextChanged(object sender, RoutedEventArgs e)
{
AutoCompleteBox box = sender as AutoCompleteBox;
if (box == null) return;
box.Text = box.Text.ToUpper();
}
That kind of works except that it writes backwards. When the user inputs a character, the cursor goes back to the start of the box so the next word is in front of the previous one. If I wrote 'example', I would see "ELPMAXE".
Any ideas?
I solved a similar problem where I only wanted entry of numbers in a textbox, so I used a behavior. If a non-number is entered, the character is deleted. I also used the interactivity library which uses the System.Windows.Interactivity.dll (just import this DLL into your project, if you don't have it its part of the blend sdk http://www.microsoft.com/en-us/download/details.aspx?id=10801).
Here is the simplified XAML:
<Window x:Class="Sample.SampleWindow"
xmlns:main="clr-namespace:MySampleApp"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="Sample"
Height="800"
Width="1025"
>
<Grid>
<TextBox Text="{Binding Path=Entry, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="30"
MaxLength="4"
HorizontalAlignment="Left">
<i:Interaction.Behaviors>
<main:KeyPressesWithArgsBehavior
KeyUpCommand="{Binding KeyUpFilterForUpperCaseSymbols}" />
</i:Interaction.Behaviors>
</TextBox>
</Grid>
</Window>
Uses the following Behavior class:
public class KeyPressesWithArgsBehavior : Behavior<UIElement>
{
#region KeyDown Press DependencyProperty
public ICommand KeyDownCommand
{
get { return (ICommand) GetValue(KeyDownCommandProperty); }
set { SetValue(KeyDownCommandProperty, value); }
}
public static readonly DependencyProperty KeyDownCommandProperty =
DependencyProperty.Register("KeyDownCommand", typeof (ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyDown Press DependencyProperty
#region KeyUp Press DependencyProperty
public ICommand KeyUpCommand
{
get { return (ICommand) GetValue(KeyUpCommandProperty); }
set { SetValue(KeyUpCommandProperty, value);}
}
public static readonly DependencyProperty KeyUpCommandProperty =
DependencyProperty.Register("KeyUpCommand", typeof(ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyUp Press DependencyProperty
protected override void OnAttached()
{
AssociatedObject.KeyDown += new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp += new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.KeyDown -= new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnDetaching();
}
private void AssociatedUIElementKeyDown(object sender, KeyEventArgs e)
{
if (KeyDownCommand != null)
{
ObjectAndArgs oa = new ObjectAndArgs {Args = e, Object = AssociatedObject};
KeyDownCommand.Execute(oa);
}
}
private void AssociatedUIElementKeyUp(object sender, KeyEventArgs e)
{
if (KeyUpCommand != null)
{
KeyUpCommand.Execute(AssociatedObject);
}
}
}
Then in your View Model you can implement the command.
SampleWindowViewModel.cs:
public ICommand KeyUpFilterForUpperCaseSymbolsCommand
{
get
{
if (_keyUpFilterForUpperCaseSymbolsCommand== null)
{
_keyUpFilterForUpperCaseSymbolsCommand= new RelayCommand(KeyUpFilterForUpperCaseSymbols);
}
return _keyUpFilterForUpperCaseSymbolsCommand;
}
}
...
private void KeyUpFilterForUpperCaseSymbols(object sender)
{
TextBox tb = sender as TextBox;
if (tb is TextBox)
{
// check for a lowercase character here
// then modify tb.Text, to exclude that character.
// Example: tb.Text = oldText.Substring(0, x);
}
}
Related
I'm attempting to have a user input textbox take an ID string and fetch data to fill rows in a datagrid, then trigger editing a specific row and cell in the datagrid based on the value of the user input and then put focus back on the textbox when the return/enter key is pressed after the user enters a value in the datagrid cell.
I have most of this working correctly except for the last step, the enter key has to be pressed twice in order to return to the text box, I would like it to return on first press.
If the edited cell is selected by the user directly using the mouse then it works correctly, a single enter press sends focus back to the textbox.
The automatic cell editing is triggered with an attached property and a helper class that listens for the OnCurrentCellChanged event and triggers the datagrid BeginEdit method.
I wonder if this needs to be closed off/ended before the focus can change properly?
(Helper class implemented based on answer here, with great thanks to #Orace)
How can I achieve this? I have tried commiting the edit, cancelling the edit in the xaml code behind PreviewKeyDown method but no luck. So possibly I have to change the OnCurrentCellChanged event method somehow?
my xaml:
<Window x:Class="TestChangingEditedCellFocus.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestChangingEditedCellFocus" d:DataContext="{d:DesignInstance Type=local:ViewModel}"
mc:Ignorable="d"
FocusManager.FocusedElement="{Binding ElementName=idfield}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="srModels" Source="{Binding Models}"/>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="idfield" Width="100" Height="30" Text="{Binding Path=ModelId, UpdateSourceTrigger=PropertyChanged}"/>
<Button IsDefault="True" Command="{Binding Path=Command}" Content="Get Row" Height="30"/>
</StackPanel>
<DataGrid Grid.Row="1"
x:Name="modelgrid"
local:DataGridAutoEdit.AutoEditColumn="2"
ItemsSource="{Binding Source={StaticResource srModels}}"
SelectedItem="{Binding CurrentModel, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
AutoGenerateColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow"
PreviewKeyDown="modelgrid_PreviewKeyDown"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Id}" HorizontalContentAlignment="Right"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name" Width="Auto" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name}" HorizontalContentAlignment="Right"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0"
VerticalContentAlignment="Center" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Right"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Xaml code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key == Key.Enter) || (e.Key == Key.Return))
{
//DataGrid grid = sender as DataGrid;
//grid.CancelEdit();
idfield.Focus();
idfield.SelectAll();
}
}
}
The VeiwModel:
public class ViewModel : ObservableObject
{
private ObservableCollection<model> _models;
private model _currentModel;
private string _modelId;
private ICommand _command;
public ObservableCollection<model> Models
{
get
{
if (_models is null)
{
_models = new ObservableCollection<model>();
OnPropertyChanged("Models");
}
return _models;
}
}
public model CurrentModel
{
get
{
return _currentModel;
}
set
{
_currentModel = value;
OnPropertyChanged("CurrentModel");
}
}
public string ModelId
{
get
{
return _modelId;
}
set
{
_modelId = value;
OnPropertyChanged("ModelId");
}
}
public ICommand Command
{
get
{
if (_command == null)
{
_command = new RelayCommand(param=>ExcecuteCommand(), pred=> ModelId is not null);
}
return _command;
}
}
public void ExcecuteCommand()
{
Models.Clear();
if (Models.Count == 0)
{
Models.Add(new model() { Id = 1, Name = "name1" });
Models.Add(new model() { Id = 2, Name = "name2" });
Models.Add(new model() { Id = 3, Name = "name3" });
Models.Add(new model() { Id = 4, Name = "name4" });
}
foreach (model model in Models)
{
System.Diagnostics.Debug.WriteLine("Model: " + model.Name);
int id = int.Parse(ModelId);
if(id == model.Id)
{
CurrentModel = model;
}
}
ModelId = null;
}
}
The DataGridAutoEdit helper class to trigger edit mode:
class DataGridAutoEdit
{
public static readonly DependencyProperty AutoEditColumnProperty = DependencyProperty.RegisterAttached("AutoEditColumn", typeof(int), typeof(DataGridAutoEdit), new PropertyMetadata(default(int), AutoEditColumnChangedCallback));
public static void SetAutoEditColumn(DependencyObject element, int value)
{
element.SetValue(AutoEditColumnProperty, value);
}
public static int GetAutoEditColumn(DependencyObject element)
{
return (int)element.GetValue(AutoEditColumnProperty);
}
private static void AutoEditColumnChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not DataGrid dataGrid)
return;
GetAutoEditColumnHelper(dataGrid)?.Dispose();
if (e.NewValue is int columnIndex)
{
SetAutoEditColumnHelper(d, new AutoEditColumnHelper(dataGrid, columnIndex));
}
else
{
d.ClearValue(AutoEditColumnHelperProperty);
}
}
private static readonly DependencyProperty AutoEditColumnHelperProperty = DependencyProperty.RegisterAttached("AutoEditColumnHelper", typeof(AutoEditColumnHelper), typeof(DataGridAutoEdit), new PropertyMetadata(default(AutoEditColumnHelper)));
private static void SetAutoEditColumnHelper(DependencyObject element, AutoEditColumnHelper value)
{
element.SetValue(AutoEditColumnHelperProperty, value);
}
private static AutoEditColumnHelper? GetAutoEditColumnHelper(DependencyObject element)
{
return element.GetValue(AutoEditColumnHelperProperty) as AutoEditColumnHelper;
}
//add private class to get datagrid auto cel edit function working, move to helpers if this works
private class AutoEditColumnHelper : IDisposable
{
private readonly DataGrid _dataGrid;
private readonly int _columnIndex;
private object? _lastItem;
public AutoEditColumnHelper(DataGrid dataGrid, int columnIndex)
{
_dataGrid = dataGrid;
_columnIndex = columnIndex;
_dataGrid.CurrentCellChanged += OnCurrentCellChanged;
}
public void Dispose()
{
_dataGrid.CurrentCellChanged -= OnCurrentCellChanged;
}
private void OnCurrentCellChanged(object? sender, EventArgs e)
{
DataGridCellInfo currentCell = _dataGrid.CurrentCell;
if (!currentCell.IsValid || Equals(currentCell.Item, _lastItem))
{
return;
}
DataGridColumn autoEditColumn = GetAutoEditColumn();
if (autoEditColumn is null)
{
return;
}
_dataGrid.Dispatcher.BeginInvoke(() =>
{
_lastItem = _dataGrid.SelectedItem;
_dataGrid.CurrentCell = new DataGridCellInfo(_lastItem, autoEditColumn);
_dataGrid.BeginEdit();
});
}
private DataGridColumn? GetAutoEditColumn()
{
return _columnIndex < 0 || _columnIndex > _dataGrid.Columns.Count ? null : _dataGrid.Columns[_columnIndex];
}
}
}
A working simplified project is here.
After some trial and error I found editing my PreviewKeyDowan method in code behind to both Commit and Cancel the edit AND using SendKeys to send the Enter command again worked to send the cursor back to the textbox feild as I wanted:
private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key == Key.Enter) || (e.Key == Key.Return))
{
DataGrid grid = sender as DataGrid;
grid.CommitEdit();
grid.CancelEdit();
SendKeys.SendWait("{ENTER}");
idfield.Focus();
}
}
but this seems pretty hacky and I'll be happy for any other suggestions.
I have a WinUI 3 ListView that displays a list of items. Every item has a ToggleSwitch and a Expander. When i click on the ToggleSwitch or the Expander the ListView selection does not change.
I found some solutions for WPF but they dont work in WinUI 3:
Selecting a Textbox Item in a Listbox does not change the selected item of the listbox
How can I do this for WinUI 3 so that the associated ListViewItem is selected when the ToggleSwitch or Expander is selected?
You could handle the Tapped event for the Expander and ToggleSwitch and programmatically set the SelectedItem property of the ListView:
private void OnTapped(object sender, TappedRoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
lv.SelectedItem = element.DataContext;
}
XAML:
<ListView x:Name="lv">
<ListView.ItemTemplate>
<DataTemplate>
...
<Expander Tapped="OnTapped" ... />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If you don't want to change selection when you do something programmatically, you can do it this way.
.xaml
<StackPanel>
<Button
Command="{x:Bind ViewModel.TestCommand}"
Content="Click" />
<ListView
x:Name="ListViewControl"
ItemsSource="{x:Bind ViewModel.Items}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleSwitch Toggled="ToggleSwitch_Toggled" />
<Expander Expanding="Expander_Expanding" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
.xaml.cs
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
}
private void Expander_Expanding(Expander sender, ExpanderExpandingEventArgs args)
{
if (ViewModel.IsProgrammatical is false)
{
ListViewControl.SelectedItem = sender.DataContext;
}
}
ViewModel.cs
public partial class Item : ObservableObject
{
[ObservableProperty]
private string text = string.Empty;
[ObservableProperty]
private bool isChecked;
}
public partial class MainWindowViewModel : ObservableObject
{
public bool IsProgrammatical { get; set; }
[ObservableProperty]
private List<Item> items = new()
{
{ new Item() { Text = "A", IsChecked = false,} },
{ new Item() { Text = "B", IsChecked = false,} },
{ new Item() { Text = "C", IsChecked = false,} },
};
[RelayCommand]
private void Test()
{
IsProgrammatical = true;
Items[1].IsChecked = !Items[1].IsChecked;
IsProgrammatical = false;
}
}
Workaround
In this case, the source collection is untouchable and we can't use a flag if the property was changed programmatically or not, we need to use the Tapped event to make the item selected. But unfortunately, the ToggleSwitch's Tapped event is not fired (at least in my environment). Might be a WinUI bug (issue posted here).
As a workaround, at least until this bug gets fixed, you can use the ToggleButton. I tested it out and the Tapped event is fired.
<DataTemplate x:DataType="local:Item">
<StackPanel>
<ToggleButton Tapped="ToggleButton_Tapped" />
<Expander Tapped="Expander_Tapped" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
private void ToggleButton_Tapped(object sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}
private void Expander_Tapped(Expander sender, TappedRoutedEventArgs e)
{
ListViewControl.SelectedItem = sender.DataContext;
}
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'm making a chating program.
I designed chat room list using XAML.
<GridViewColumn x:Name="gridViewColumn_IsNeedPassword">
<GridViewColumn.CellTemplate>
<DataTemplate>
<PasswordBox x:Name="passwordBox_PW" MinWidth="100" IsEnabled="{Binding Path=IsNeedPassword}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="gridViewColumn_EntryButton">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Background="Aqua" Click="button_Entry_Click">
<StackPanel Orientation="Horizontal">
<Image Height="Auto" Width="Auto" Source="Resources/login.png"/>
<TextBlock Text="{Binding Converter={StaticResource EntryButtonConverter}}" VerticalAlignment="Center"/>
</StackPanel>
<Button.Tag>
<MultiBinding Converter="{StaticResource EntryButtonTagConverter}">
<Binding Path="ID"/>
<Binding Path="IsNeedPassword"/>
<Binding ElementName="passwordBox_PW" Path="Password"/>
</MultiBinding>
</Button.Tag>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn x:Name="gridViewColumn_DeleteButton">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Background="Orange" Click="button_Delete_Click" IsEnabled="{Binding Path=Master, Converter={StaticResource DeleteButtonVisibilityConverter}}">
<StackPanel Orientation="Horizontal">
<Image Height="Auto" Width="Auto" Source="Resources/login.png"/>
<TextBlock Text="{Binding Converter={StaticResource DeleteButtonConverter}}" VerticalAlignment="Center"/>
</StackPanel>
<Button.Tag>
<Binding Path="ID"/>
</Button.Tag>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
Something like this.
Now, in the gridViewColumn_EntryButton I need some infos such as RoomID + IsNeedPassword + PasswordText
So i used MultiBinding.
and the EntryButtonTagConverter.Convert is like that.
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string[] result = Array.ConvertAll<object, string>(values, obj =>
{
return (obj == null) ? string.Empty : obj.ToString();
});
// RoomID + IsNeedPassword + PasswordText
return result[0] + '\n' + result[1] + '\n' + result[2];
}
and When i debugging, the result[2], PasswordText is "{DependencyProperty.UnsetValue}"
But i inputed into the PasswordBox asdftest1234.
I don't know why PasswordBox.Password property is not accessable.
Any one some ideas?
Thanks.
It´s not possible to bind directly to the PasswordProperty for security reasons.
Take a look here!
Using PasswordBoxAssistant you can bind password.
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPasswordProperty =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string)
, typeof(PasswordBoxAssistant), new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPasswordProperty = DependencyProperty.RegisterAttached(
"BindPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPasswordProperty =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
var ignoreBindProperty = false;
if (box != null && box.Parent != null)
{// TODO: Bind property change not set in case of hosting password box under Telerik datafield. That why I am ignoring the bind propery here - Morshed
ignoreBindProperty = (box.Parent is Telerik.Windows.Controls.DataFormDataField);
}
if (d == null || !(GetBindPassword(d) || ignoreBindProperty))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPasswordProperty, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPasswordProperty);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPasswordProperty);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPasswordProperty, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPasswordProperty);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPasswordProperty, value);
}
}
and change in xaml
<PasswordBox helpers:PasswordBoxAssistant.BindPassword="true"
helpers:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true}"/>
I wanted to set the command of a button in a WPF datagrid with a setter. But it seems that the DP property CommandProperty gets ovewritten with its default value null after I returned a copy in
CreateInstanceCore(), so the original command gets lost.
If I bind the StaticResource directly it works without problems.
Is there a way to stop that behavior or another solution?
public class ResourceCommand : Freezable, ICommand {
public ICommand Command {
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ResourceCommand), new UIPropertyMetadata(null, CommandPropertyChangedCallback));
static void CommandPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ResourceCommand resourceCommand = (ResourceCommand)d;
int h = resourceCommand.GetHashCode();
if (e.OldValue != null)
((ICommand)e.OldValue).CanExecuteChanged -= resourceCommand.OnCanExecuteChanged;
if (e.NewValue != null)
((ICommand)e.NewValue).CanExecuteChanged += resourceCommand.OnCanExecuteChanged;
}
#region ICommand Member
public bool CanExecute(object parameter) {
if (Command == null)
return false;
return Command.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged;
void OnCanExecuteChanged(object sender, EventArgs e) {
if (CanExecuteChanged != null)
CanExecuteChanged(sender, e);
}
public void Execute(object parameter) {
Command.Execute(parameter);
}
#endregion
protected override Freezable CreateInstanceCore() {
ResourceCommand ResourceCommand = new ResourceCommand();
ResourceCommand.Command = Command;
return ResourceCommand;
}
}
xaml:
<Window.Resources>
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" />
<local:ResourceCommand x:Key="SecondCommand" Command="{Binding SecondCommand}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Click me">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Command" Value="{StaticResource FirstCommand}" />
</Style>
</Button.Style></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
It works if you define your resource commands like this:
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" x:Shared="False"/>
Using this technique you can even throw not-implemented in CreateInstanceCore and so you'll just be using Freezable to enable data binding.