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
Related
I am a VB.Net programmer and quite new to C#. I am at a point where I'm stuck.
I want to make an app to create a quotation with Word. This Quotation should consist of two Word files.
The Word files are Templates with Bookmarks, so writing to them should be no problem.
I want to have a WPF User Interface where the User can describe the Article and when clicking on a button the two Word files will be created.
I made the WPF User Interface and binded the Textboxes to a cl_Data.cs Class where are Properties such as : Description, FunctionName, etc.
My Problem:
How can i access the Data from the User Interface from my Code Behinde to shift it to the Word files?
The Code:
WPF: How i Bind it on .xaml level
<Window.Resources>
<!-- Binding the Data Class-->
<local:Cl_Data x:Key="Data"
Dealer="Test"
Customer="Tester"
Machine="M***s"
PRJ="123456"
DeliveryTime="6"
Description="Managing different chucks, Saving position data of the linear sensor for chuck clamp unclamp position"
Operation="The operator can select a chuck form the chuck management and save the clamp and unclamp position and reuse this position for next time"
FunctionName="GeneratorAPP"
Requirements="API-Kit"
/>
</Window.Resources>
How i call it on .xaml level (same document) -> This works
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer"
TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border BorderBrush="#FFB0F0FF" BorderThickness="1" Height="26">
<TextBox x:Name="Tb_Dealer" TextWrapping="Wrap" Text="{Binding Dealer, UpdateSourceTrigger=PropertyChanged}" Width="auto" Foreground="#FFB0F0FF" BorderBrush="#00ABADB3" Background="Transparent" TextAlignment="Center" VerticalAlignment="Center" />
</Border>
So my class cl_Data.cs looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Windows;
namespace QuotationApp.Classes
{
internal class Cl_Data : INotifyPropertyChanged
{
#region Descriptions
private string _Dealer ;
public string Dealer
{
get { return _Dealer; }
set
{ _Dealer = value;
OnPropertyChanged("Dealer");
}
}
private string _Customer;
public string Customer
{
get { return _Customer; }
set
{
_Customer = value;
OnPropertyChanged("Customer");
}
}
private string _Machine;
public string Machine
{
get { return _Machine; }
set
{
_Machine = value;
OnPropertyChanged("Machine");
}
}
private string _PRJ;
public string PRJ
{
get { return _PRJ; }
set
{
_PRJ = value;
OnPropertyChanged(PRJ);
}
}
private string _DeliveryTime;
public string DeliveryTime
{
get { return _DeliveryTime; }
set {
_DeliveryTime = value;
OnPropertyChanged("DeliveryTime");
}
}
private string _Operation;
public string Operation
{
get { return _Operation; }
set {
_Operation = value;
OnPropertyChanged("Operation");
}
}
private string _Description;
public string Description
{
get { return _Description; }
set {
_Description = value;
OnPropertyChanged("Description");
}
}
private string _FunctionName;
public string FunctionName
{
get { return _FunctionName; }
set {
_FunctionName = value;
OnPropertyChanged("FunctionName");
}
}
private string _Requirements;
public string Requirements
{
get { return _Requirements; }
set {
_Requirements = value;
OnPropertyChanged("Requirements");
}
}
#endregion
#region Costs
private double _HardwareCost;
public double HardwareCost
{
get { return _HardwareCost; }
set {
_HardwareCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("HardwareCost");
}
}
private double _PersonalCost;
public double PersonalCost
{
get { return _PersonalCost; }
set {
_PersonalCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("PersonalCost");
}
}
private double _TravelCost;
public double TravelCost
{
get { return _TravelCost; }
set {
_TravelCost = value;
_CostTotal = CalcTotal();
OnPropertyChanged("TravelCost");
}
}
private double _CostTotal;
public double CostTotal
{
get { return _CostTotal; }
set {
_CostTotal = value;
OnPropertyChanged("CostTotal");
}
}
public double CalcTotal()
{
double total = 0;
try
{
total = TravelCost + HardwareCost + PersonalCost;
}
catch (Exception e)
{
MessageBox.Show("Error getting the total Value: " + e.Message);
}
return total;
}
#endregion
#region PropertyChangedEvents
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
So now i want to access these Data for example the Description (Data.Description) to process it to a word Bookmark. But how can i Access this Data on WPF level from CodeBehind?
Please be easy with me, i know this question is wierd but i googled 2 days now an i am starting to get frustrated. If this question is answered somewhere else, i would love to have the link to the answer.
Thanks in advance
I made the most simple example as far as it came to my mind.
If you don't understand, ask questions about it.
I will try to answer.
using System;
namespace Core2022.Lexxy_B
{
public class PersonDto
{
public int Id { get; }
public string Name { get; }
public int Age { get; }
public PersonDto(int id, string name, int age)
{
if (Id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
Id = id;
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
Name = name;
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
Age = age;
}
public PersonDto(string name, int age)
: this(0, name, age)
{
Id = -1;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
namespace Core2022.Lexxy_B
{
public class PeopleModel
{
private readonly List<PersonDto> people = new List<PersonDto>()
{
new PersonDto(5, "Thomas", 25),
new PersonDto(1, "Harry", 40),
};
public IReadOnlyList<PersonDto> GetPeople() => Array.AsReadOnly(people.ToArray());
public void AddPerson(PersonDto person)
{
int id = people.LastOrDefault()?.Id ?? 0;
do
{
id++;
} while (people.Any(p => p.Id == id));
person = new PersonDto(id, person.Name, person.Age);
people.Add(person);
AddedPerson?.Invoke(this, person);
}
public event EventHandler<PersonDto>? AddedPerson;
}
}
namespace Core2022.Lexxy_B
{
public class PersonVM
{
public string? Name { get; set; }
public int Age { get; set; }
}
}
using Simplified;
using System.Collections.ObjectModel;
namespace Core2022.Lexxy_B
{
public class PeopleViewModel : ViewModelBase
{
private readonly PeopleModel model = new PeopleModel();
private string _mode = "view";
public ObservableCollection<PersonDto> People { get; } = new ObservableCollection<PersonDto>();
public string ViewMode { get => _mode; private set => Set(ref _mode, value); }
public PeopleViewModel()
{
foreach (var person in model.GetPeople())
{
People.Add(person);
}
model.AddedPerson += OnAddedPerson;
}
private void OnAddedPerson(object? sender, PersonDto newPerson)
{
People.Add(newPerson);
}
public RelayCommand AddPersonCommand => GetCommand<PersonVM>(AddPersonExecute, AddPersonCanExecute);
private void AddPersonExecute(PersonVM person)
{
model.AddPerson(new PersonDto(person.Name ?? string.Empty, person.Age));
ViewMode = "view";
}
private bool AddPersonCanExecute(PersonVM person)
{
return !string.IsNullOrWhiteSpace(person.Name) && person.Age >= 0;
}
public RelayCommand ExitAddingPersonCommand => GetCommand(() => ViewMode = "view");
public RelayCommand BeginAddingPersonCommand => GetCommand(() => ViewMode = "add");
}
}
<Window x:Class="Core2022.Lexxy_B.PeopleWindow"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
Title="PeopleWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:PeopleViewModel x:Key="vm"/>
</Window.Resources>
<UniformGrid Columns="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="lBox" ItemsSource="{Binding People}" DisplayMemberPath="Name"/>
<Button Grid.Row="1" Content="Go to Add Person" Padding="15 5" Margin="5"
Command="{Binding BeginAddingPersonCommand}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="add">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<ContentControl x:Name="cp">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Resources>
<DataTemplate x:Key="view.Template">
<local:PersonDetailsUC/>
</DataTemplate>
<DataTemplate x:Key="add.Template">
<local:AddPersonUC/>
</DataTemplate>
</Style.Resources>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate" Value="{StaticResource add.Template}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewMode}" Value="view">
<Setter Property="Content" Value="{Binding SelectedItem, ElementName=lBox}"/>
<Setter Property="ContentTemplate" Value="{StaticResource view.Template}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</UniformGrid>
</Window>
<UserControl x:Class="Core2022.Lexxy_B.PersonDetailsUC"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:PersonDto}">
<UniformGrid Columns="1">
<TextBlock Text="{Binding Id, StringFormat={}Id: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Name, StringFormat={}Name: {0}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding Age, StringFormat={}Age: {0}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</UniformGrid>
</UserControl>
<UserControl x:Class="Core2022.Lexxy_B.AddPersonUC"
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:Core2022.Lexxy_B"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<d:UserControl.DataContext>
<local:PeopleViewModel/>
</d:UserControl.DataContext>
<UserControl.Resources>
<local:PersonVM x:Key="person"/>
</UserControl.Resources>
<UniformGrid Columns="2">
<TextBlock Text="Name" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Name, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<TextBlock Text="Age" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Text="{Binding Age, Source={StaticResource person}}" VerticalAlignment="Center" Margin="10"/>
<Button Content="Add" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding AddPersonCommand}"
CommandParameter="{Binding Mode=OneWay, Source={StaticResource person}}"/>
<Button Content="Exit" Padding="15 5" VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding ExitAddingPersonCommand}"/>
</UniformGrid>
</UserControl>
Addition due to the Repository.
change Line 33 of Word.cs as i was unable to assign a relative path to open to word template
An example of the implementation of obtaining the full name of files by their path given by a relatively executable assembly.
The file itself in the Project resources must have the properties "Content" - "Copy ...". I have a Russified Studio, so the screenshot is in Russian.
The "bin" folder must not be included in the Project, otherwise, all its contents will also be included in the Assembly.
And here is the code for converting a relative path to an absolute one:
public static readonly string ApplicationFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public const string TestRelativeNameFile = #"Resources\Word\Test.docx";
public static readonly string TestFullNameFile = Path.Combine(ApplicationFolder, TestRelativeNameFile);
public static void GenerateLocalSolution()
{
try
{
WordApp = new Microsoft.Office.Interop.Word.Application();
TestApp = WordApp.Documents.Open(TestFullNameFile);
}
i bind the DataContext in MainWindow.cs (CodeBehind) to a Class and create a specific Instance of the Object which i access from every other class
This implementation doesn't play well with WPF and OOP.
For example, in Designer mode (when you edit XAML in Studio), you have an empty value in the DataContext. And because of this, you can't use the Binding Builder in development.
If you have an instance of the Cl_Data class specific to each MainWindow instance, then it should be initialized in the Window's XAML:
<Window.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Window.Resources>
If the Cl_Data instance is the only one in the application sense and the entire session exists, then you can create it in the App resources:
<Application.Resources>
<classes:Cl_Data
xmlns:classes="clr-namespace:QuotationApp.Classes"
x:Key="data"/>
</Application.Resources>
The window gets it in the DataContext in XAML too.
If its value is needed in the Code Behind, then it must be retrieved either by key or from the DataContext.
<Window x:Class="QuoteApp_EldHasp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
------------------
Title="MainWindow" Height="900" Width="1800"
DataContext="{DynamicResource data}">
// Creating such a public property is a bad idea. But for now, I'm not getting involved.
public /* static */ Cl_Data Data { get; } //= new Cl_Data();
public MainWindow()
{
InitializeComponent();
Data = (Cl_Data)DataContext;
// Or
Data = (Cl_Data)FindResource("data");
}
Next note.
In the Word.GenerateLocalSolution() method, the line "descRange.Text = MainWindow.Data.Dealer" is a VERY STRONG OOP violation. The Word class contains logic for working with data (Domain Logic) - this is its "single-responsibility". And it should not work with UI elements. And "MainWindow.Data" is the View property!
There are several options for correct implementation.
One of them is to get the desired value in the parameter and let the one who called this method decide where to get it from.
public static void GenerateLocalSolution(string text)
{
// Some Code
descRange.Text = text; // MainWindow.Data.Dealer;
private void GenerateButton_Click(object sender, RoutedEventArgs e)
{
var data = (Cl_Data)DataContext;
Word.GenerateLocalSolution(data.Dealer);
}
You should also replace clickers with commands. This will significantly improve the architecture of the application and make the code easier.
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 learning C# and building a UI that reads and writes integers to an XML config file. The UI uses a variety of custom user controls. I have a 3 radiobutton user control that binds to a single int variable (control returns 0,1,2). The control uses an event to trigger the update. It looks at the 3 isChecked properties to determine the new int value. But I don’t know how to update the original binding value from the code behind. It's once removed so to speak because there are two binds..one in the main window and one in the user control. As a beginner am lost at this point. BTW reading the int value into the 3 radiobuttons is working using a converter.
here is the user control xaml.cs...
namespace btsRV7config.controls
{
public partial class ui3X : UserControl
{
public ui3X()
{
InitializeComponent();
}
void _event(object sender, RoutedEventArgs e)
{
int newValue = 0;
if (rdbttn1.IsChecked == true) { newValue = 0; }
else if (rdbttn2.IsChecked == true) { newValue = 1; }
else if (rdbttn3.IsChecked == true) { newValue = 2; }
txtb.Text = newValue.ToString(); //tempRemove
// !!! assign newValue to Binding Source !!!
//---------------------------------------------
uiBinding1 = newValue;
BindingOperations.GetBindingExpression(rdbttn1, RadioButton.IsCheckedProperty).UpdateSource();
//---------------------------------------------
}
public static readonly DependencyProperty uiBinding1Property = DependencyProperty.Register("uiBinding1", typeof(int), typeof(ui3X));
public int uiBinding1
{
get { return (int)GetValue(uiBinding1Property); }
set { SetValue(uiBinding1Property, value); }
}
public static readonly DependencyProperty uiBinding2Property = DependencyProperty.Register("uiBinding2", typeof(int), typeof(ui3X));
public int uiBinding2
{
get { return (int)GetValue(uiBinding2Property); }
set { SetValue(uiBinding2Property, value); }
}
public static readonly DependencyProperty uiBinding3Property = DependencyProperty.Register("uiBinding3", typeof(int), typeof(ui3X));
public int uiBinding3
{
get { return (int)GetValue(uiBinding3Property); }
set { SetValue(uiBinding3Property, value); }
}
}
}
here is user control xaml
<UserControl x:Class="btsRV7config.controls.ui3X"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Horizontal" Height="22">
<RadioButton Name="rdbttn1" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding1}"
Click="_event" />
<RadioButton Name="rdbttn2" VerticalAlignment="Center" Margin="0 0 10 0"
IsChecked="{Binding ElementName=root, Path=uiBinding2}"
Click="_event" />
<RadioButton Name="rdbttn3" VerticalAlignment="Center"
IsChecked="{Binding ElementName=root, Path=uiBinding3}"
Click="_event" />
<TextBox Name="txtb" Margin="5 0 0 0" Width="20" Height="17" /> <!-- tempRemove -->
</StackPanel>
</UserControl>
here is an example of the user control used in MainWindow.xaml
<Window x:Class="btsRV7config.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:btsRV7config.controls"
xmlns:converter="clr-namespace:btsRV7config.converters"
Title="Vans RV7 Configuration" Height="350" Width="525" >
<Window.Resources>
<converter:Int_Int_Bool_Converter x:Key="Int_Int_Bool" />
</Window.Resources>
<Grid>
<controls:ui3X uiName="Font Color" ui1="Black" ui2="Green" ui3="Cyan"
uiBinding1="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=0}"
uiBinding2="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=1}"
uiBinding3="{Binding RV7sld_DFfontColor, Converter={StaticResource Int_Int_Bool}, ConverterParameter=2}" />
</Grid>
</Window>
here is MainWindow.xaml.cs
namespace btsRV7config
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
record data = new record();
DataContext = data;
}
}
public class record : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _RV7sld_DFfontColor = RV7sld_dict["DFfontColor"];
public int RV7sld_DFfontColor
{
get
{ return _RV7sld_DFfontColor; }
set
{
_RV7sld_DFfontColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("RV7sld_DFfontColor"));
}
}
}
}
}
Sorry for posting so much code - I think the important is the user controls xaml.cs at top.
here is a link to a picture of the UI.
I've simplified the code I've posted to fit.
http://www.baytower.ca/photo/uiSample.jpg
So - 'Font Color'(RV7sld_DFfontColor) can be black(0) green(1) cyan(2)
Danny
The BindingOperations class enables you to "force" the binding updates in either direction.
Let's say there is a string property X in the code behind and there is a TextBox in XAML, bound to that property:
// C#:
public string X { get; set; }
// XAML:
<TextBox Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}, Path=X}" />
To copy from textBox1.Text to X do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateSource();
To copy from X to textBox1.Text do the following:
BindingOperations.GetBindingExpression(textBox1, TextBox.TextProperty).UpdateTarget();
I have the following class:
public class LooklessControl : Control
{
public List<int> IntList { get; private set; }
public int CurrentInt { get; private set; }
private int _index = 0;
static LooklessControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LooklessControl), new FrameworkPropertyMetadata(typeof(LooklessControl)));
}
public LooklessControl()
{
IntList = new List<int>();
for (int i = 0; i < 10; i++)
{
IntList.Add(i);
}
CurrentInt = IntList[_index];
}
public static readonly RoutedCommand NextItemCommand =
new RoutedCommand("NextItemCommand", typeof(LooklessControl));
private void ExecutedNextItemCommand(object sender, ExecutedRoutedEventArgs e)
{
NextItemHandler();
}
private void CanExecuteNextItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedCommand PrevItemCommand =
new RoutedCommand("PrevItemCommand", typeof(LooklessControl));
private void ExecutedPrevItemCommand(ExecutedRoutedEventArgs e)
{
PrevItemHandler();
}
private void CanExecutePrevItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedEvent NextItemEvent =
EventManager.RegisterRoutedEvent("NextItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler NextItem
{
add { AddHandler(NextItemEvent, value); }
remove { RemoveHandler(NextItemEvent, value); }
}
private void RaiseNextItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.NextItemEvent);
RaiseEvent(args);
}
public static readonly RoutedEvent PrevItemEvent =
EventManager.RegisterRoutedEvent("PrevItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler PrevItem
{
add { AddHandler(PrevItemEvent, value); }
remove { RemoveHandler(PrevItemEvent, value); }
}
private void RaisePrevItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.PrevItemEvent);
RaiseEvent(args);
}
private void NextItemHandler()
{
_index++;
if (_index == IntList.Count)
{
_index = 0;
}
CurrentInt = IntList[_index];
RaiseNextItemEvent();
}
private void PrevItemHandler()
{
_index--;
if (_index == 0)
{
_index = IntList.Count - 1;
}
CurrentInt = IntList[_index];
RaisePrevItemEvent();
}
}
The class has a default style, in Generic.xaml, that looks like this:
<Style x:Key="{x:Type local:LooklessControl}" TargetType="{x:Type local:LooklessControl}">
<Setter Property="Height" Value="200"/>
<Setter Property="Width" Value="90"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LooklessControl}">
<Border BorderBrush="Black" BorderThickness="1" Padding="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="LightGray"/>
<Rectangle Grid.Row="1" Fill="Gainsboro"/>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
<TextBlock Grid.Column="1" Name="textBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CurrentInt}"
HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Junction" FontSize="13"/>
<Path Grid.Column="2" x:Name="pathRightArrow" Data="M0,0 L1,0.5 0,1Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
</Grid>
<ListBox Grid.Row="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="Transparent"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntList}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How do I make it so that when the user clicks on pathLeftArrow it fires LooklessControl.PrevItemCommand, or or they click on pathRightArrow and it fires LooklessControl.NextItemCommand, or they click on an item in the ListBox and LooklessControl is notified of the newly selected item?
In other words, without adding x:Class to the top of Generic.xaml and thus creating a code-behind file for it, which I assume you wouldn't want to do, how do you handle events for elements in your xaml that don't have a Command property (which is just about everything other than a Button)?
Should LooklessControl have it's own XAML file (much like what you get when you create a new UserControl) associated with it that Generic.xaml just pulls in as a MergedDictionar as its default template? Or is there some other acknowledged way to do what I'm trying to do?
To answer your last question: NO. The lookless control shouldn't require any known XAML. That is what lookless means.
You have a couple of options here, but I would recommend wrapping your elements in Buttons with a basically empty control template:
<ControlTemplate x:Key="contentOnlyButton" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<Button Grid.Column="0" Template="{StaticResource contentOnlyButton}"
Command="{x:Static local:LooklessControl.PrevItemCommand}">
<Path x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14"
Stretch="Fill" HorizontalAlignment="Center" Fill="SlateBlue"/>
</Button>
Your other option (and I would say this is probably not what you should do for executing commands on clicks, but may be applicable in other circumstances), would be to look for the named part in your template in OnApplyTemplate, and wire up the events.
public override void OnApplyTemplate()
{
var prevElement = this.GetTemplateChild("PART_PathLeftArrow") as UIElement;
if (prevElement != null)
prevElement.MouseDown += (o, e) => PrevItemHandler();
...
}
One thing to note with doing this is that the Template isn't required to define the parts you are looking for, so you need to gracefully check for that circumstance. Throwing NullReferenceExceptions here will make restyling your control a royal pain for designers / developers who accidentally delete a required element. You will also want to follow the standard practice of naming your required elements with a PART_ syntax, and decorating your class with TemplatePart attributes.
[TemplatePart(Name = "PART_PathLeftArrow", Type = typeof(UIElement))]
[TemplatePart(Name = "PART_PathRightArrow", Type = typeof(UIElement))]
...
public class LooklessControl : Control
Edit: In order for the Button's to respond to the clicks, you need to setup CommandBindings to your functions that you had already defined. You would do this as a class command binding like so:
static LooklessControl()
{
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(NextItemCommand, ExecutedNextItemCommand, CanExecuteNextItemCommand));
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(PrevItemCommand, ExecutedPrevItemCommand, CanExecutePrevItemCommand));
}
The reason to do a class command binding is that if you add it to your control's CommandBindings collection, somebody using your control could inadvertently remove them. Also remember to update your command handling methods to have static semantics.
I have the following class:
public class Day
{
public int Date { get; set; }
public String DayName { get; set; }
public Day()
{
}
public Day(int date, string dayName)
{
Date = date;
DayName = dayName;
CommandManager.RegisterClassCommandBinding(typeof(Day), new CommandBinding(DayClick, new ExecutedRoutedEventHandler(OnExecutedDayClick),
new CanExecuteRoutedEventHandler(OnCanExecuteDayClick)));
}
public static readonly RoutedCommand DayClick = new RoutedCommand("DayClick", typeof(Day));
private static void OnCanExecuteDayClick(object sender, CanExecuteRoutedEventArgs e)
{
((Day)sender).OnCanExecuteDayClick(e);
}
private static void OnExecutedDayClick(object sender, ExecutedRoutedEventArgs e)
{
((Day)sender).OnExecutedDayClick(e);
}
protected virtual void OnCanExecuteDayClick(CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = false;
}
protected virtual void OnExecutedDayClick(ExecutedRoutedEventArgs e)
{
string content = String.Format("Day {0}, which is {1}, was clicked.", Date.ToString(), DayName);
MessageBox.Show(content);
e.Handled = true;
}
}
I am using the following DataTemplate (that is in a ResourceDictionary) to render it:
<DataTemplate DataType="{x:Type local:Day}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle Grid.ColumnSpan="2" x:Name="rectHasEntry" Fill="WhiteSmoke"/>
<TextBlock Grid.Column="0" x:Name="textBlockDayName" Text="{Binding DayName}"
FontFamily="Junction" FontSize="11" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<TextBlock Grid.Column="1" x:Name="textBlockDate" Text="{Binding Date}"
FontFamily="Junction" FontSize="11" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/>
<Rectangle Grid.ColumnSpan="2" x:Name="rectMouseOver" Fill="#A2C0DA" Opacity="0"
Style="{StaticResource DayRectangleMouseOverStyle}">
</Rectangle>
</Grid>
</DataTemplate>
No problems so far, I can get it on screen.
What I want to be able to do is assign a Command, or use an event, so that when the user clicks on the Day it will notify the parent of the Day object that it has been clicked.
I've tried the following:
<Rectangle.CommandBindings>
<CommandBinding Command="{x:Static local:Day.NextDay}"
Executed="{x:Static local:Day.OnExecutedDayClick}"
CanExecute="{x:Static local:Day.OnCanExecuteDayClick}"/>
</Rectangle.CommandBindings>
to try and bind the commands that are in the Day class but it didn't work. I got an error stating:
'ResourceDictionary' root element requires a x:Class attribute to support event handlers in the XAML file. Either remove the event handler for the Executed event, or add a x:Class attribute to the root element.
Which I think means that there is no code-behind file for a ResourceDictionary, or something to that effect.
In any event, I'm not sure if I should be using Commands here, or somehow tying events to the Rectangle in question, or if this is even possible. I've seen various places where it sure looks like it's possible, I'm just unable to translate what I'm seeing into something that actually works, hence this post.
Thanks in advance.
You cann't declare CommandBinding here, in this case you can assign the command here in DataTemplate and declare CommandBinding in your main Window or Page.
Edit:
In this way you can use Commands with your custom control.
Create a custom control and Declare Commands and Command Bindings also inside the control itself as in this Sample.
MyCustomControl.cs
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
InitializeCommands();
}
private static RoutedCommand _myCommand;
public static RoutedCommand MyCommand
{
get
{
return _myCommand;
}
}
private static void InitializeCommands()
{
_myCommand = new RoutedCommand("MyCommand", typeof(MyCustomControl));
CommandManager.RegisterClassCommandBinding(typeof(MyCustomControl),
new CommandBinding(_myCommand , OnMyCommandExecute));
}
private static void OnMyCommandExecute(object sender, ExecutedRoutedEventArgs e)
{
MyCustomControl control = sender as MyCustomControl;
if (control != null)
{
//logic for this command
}
}
and in your generic.xaml write this style and assign commands like this:
generic.xaml
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<Grid>
<RepeatButton Command="{x:Static local:MyCustomControl.MyCommand}" >Click</RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>