我已经使用DataGrid编写了WPF UserControl。我添加了使用“单击编辑”编辑一列(RecordingName)的功能(请参阅:我的代码和http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing)。我也在为整个DataGrid处理MouseDoubleClick事件。
它有效......有点......你当然可以编辑有问题的列(RecordingName),当你双击该列以外的任何地方时,一切都很好。当你双击你遇到的那一列时,就是这样。这对我来说并不太令人惊讶。您正尝试捕获双击,但您也在查看单击(通过PreviewMouseLeftButtonDown事件)。
我认为这是一个常见问题。有人可以建议我处理这个问题的最佳方法吗?我绝对需要支持双击,但是也可以通过单击编辑来编辑RecordingName。
我还想支持通过右键单击并选择重命名来编辑RecordingName,并通过F2选择它。如果您转到Windows资源管理器,这是您看到的行为。如果选择该文件然后左键单击它,则表示您处于编辑(重命名)模式。如果您快速双击它,则会启动该文件。如果右键单击,或选择并按F2,则可以重命名。
感谢您的任何帮助或想法。我已粘贴下面的代码。我确实尝试将其截断到最低限度。它仍然是相当多的代码。无论好坏,我使用MVVM模型来控制自身。
这是控件的xaml:
<UserControl x:Class="StackOverFlowExample.RecordingListControl"
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:StackOverFlowExample"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
</Style>
<Style x:Key="CellViewStyle" TargetType="{x:Type Label}" BasedOn="{StaticResource {x:Type Label}}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGrid}" >
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RecordingListControl}}, Path=Foreground}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- style to apply to DataGridTextColumn in edit mode -->
<Style x:Key="CellEditStyle" TargetType="{x:Type TextBox}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="CellNonEditStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid >
<Grid Name="LayoutRoot">
<DataGrid Name="MainDataGrid" IsEnabled="{Binding Path=IsEnabled}" ItemsSource="{Binding Path=Recordings}" Margin="5" SelectionChanged="ListBox_SelectionChanged" MouseDoubleClick="DataGrid_MouseDoubleClick" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="#" IsReadOnly="True" Binding="{Binding RecordingNumber}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalAlignment"
Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn SortMemberPath="RecordingName" Header="Recording Name" CanUserSort="True">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content ="{Binding RecordingName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" Foreground="Black" Style="{StaticResource CellViewStyle}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding RecordingName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Time" CanUserSort="False" IsReadOnly="True" Binding="{Binding TotalTime, StringFormat=mm\\:ss}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="End Time" IsReadOnly="True" SortMemberPath="EndTime" Binding="{Binding EndTime,StringFormat={}\{0:dd/MM/yyyy HH:mm\}}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
<DataGrid.Resources>
</DataGrid.Resources>
<DataGrid.ContextMenu >
<ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="Delete Recording"
Command="{Binding Path=DataContext.DeleteRecordingCommand}"
CommandParameter="{Binding Path=SelectedItem}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Grid>
这里是后面的代码(需要这个依赖属性。我不知道另一种方式)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace StackOverFlowExample
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class RecordingListControl : UserControl
{
public delegate void SelectionEventHandler(object sender, RecordingInfo info);
public event SelectionEventHandler DoubleClickEvent;
public RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
LayoutRoot.DataContext = vm;
}
#region Dependency property for SelectedItem
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(RecordingInfo), typeof(RecordingListControl));
public RecordingInfo SelectedItem
{
get { return (RecordingInfo)GetValue(SelectedItemProperty); }
set
{
SetValue(SelectedItemProperty, value);
}
}
#endregion
public static FrameworkPropertyMetadata md = new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSomeCallback));
public static readonly DependencyProperty SomeDependencyProperty =
DependencyProperty.Register("SomeDependency", typeof(bool), typeof(RecordingListControl), md);
public bool SomeDependency
{
get { return (bool)GetValue(SomeDependencyProperty); }
set { SetValue(SomeDependencyProperty, value); }
}
private static void OnSomeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RecordingListControl ctrl = (RecordingListControl)d;
ctrl.vm.PopulateGrid();
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems != null && e.AddedItems.Count > 0)
SelectedItem = e.AddedItems[0] as RecordingInfo;
else
SelectedItem = null;
}
private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (MainDataGrid.SelectedItem == null || SelectedItem == null)
return;
if (DoubleClickEvent != null)
{
DoubleClickEvent(sender, SelectedItem);
}
}
// from: http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing
// SINGLE CLICK EDITING
//
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
if (!cell.IsFocused)
{
cell.Focus();
}
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid != null)
{
if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
cell.IsSelected = true;
}
else
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
}
}
}
static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
T correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
}
}
ViewModel:
public class RecordingInfo
{
public string RecordingName { get; set; }
public int RecordingNumber { get; set; }
public TimeSpan? TotalTime { get; set; }
public DateTime? EndTime { get; set; }
}
public class RecordingListViewModel : ViewModelBase
{
private ObservableCollection<RecordingInfo> _recordings = null;
private string _patientId;
private int _sessionNumber;
BackgroundWorker _workerThread = new BackgroundWorker();
public RecordingListViewModel()
{
_workerThread.DoWork += new DoWorkEventHandler(workerThread_DoWork);
_workerThread.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(workerThread_RunWorkerCompleted);
}
public ObservableCollection<RecordingInfo> Recordings
{
get
{
return _recordings;
}
}
bool _isEnabled = false;
public bool IsEnabled
{
get
{
return _isEnabled;
}
private set
{
if (value != _isEnabled)
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
}
public void PopulateGrid()
{
_workerThread.RunWorkerAsync(); // this is overkill in this demo project...
}
private void workerThread_DoWork(object sender, DoWorkEventArgs e)
{
_recordings = new ObservableCollection<RecordingInfo>();
RecordingInfo info1 = new RecordingInfo() { TotalTime = new TimeSpan(100), EndTime = DateTime.Now, RecordingName = "recording 1", RecordingNumber = 1 };
_recordings.Add(info1);
RecordingInfo info2= new RecordingInfo() { TotalTime = new TimeSpan(10000), EndTime = new DateTime(1999,2,2), RecordingName = "recording 2", RecordingNumber = 2 };
_recordings.Add(info2);
RecordingInfo info3 = new RecordingInfo() { TotalTime = new TimeSpan(7000), EndTime = new DateTime(2008, 2, 2), RecordingName = "recording 3", RecordingNumber = 3};
_recordings.Add(info3);
}
private void workerThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
OnPropertyChanged("Recordings");
IsEnabled = true;
}
public void SaveRecording(RecordingInfo info)
{
}
private RecordingInfo _selectedItem = null;
public RecordingInfo SelectedItem
{
get { return _selectedItem; }
set
{
if (value == _selectedItem)
return;
// verify that selected item is actully in our collection of recordings!
if (!_recordings.Contains(value))
throw new ApplicationException("Selected item not in collection");
_selectedItem = value;
OnPropertyChanged("SelectedItem");
// selection changed - do something special
}
}
private ICommand _deleteRecordingCmd = null;
public ICommand DeleteRecordingCommand
{
get
{
if (_deleteRecordingCmd == null)
{
_deleteRecordingCmd = new RelayCommand(param => DeleteRecordingCommandImplementation(param));
}
return _deleteRecordingCmd;
}
}
/// <summary>
/// I used ideas from this post to get Delete working:
/// http://stackoverflow.com/questions/19447795/command-bind-to-contextmenu-which-on-listboxitem-in-listbox-dont-work
/// </summary>
/// <param name="note"></param>
private void DeleteRecordingCommandImplementation(object recording)
{
if (_recordings != null && _recordings.Count > 0 && recording is RecordingInfo)
{
if (_recordings.Contains(recording as RecordingInfo))
{
_recordings.Remove(recording as RecordingInfo);
}
OnPropertyChanged("Recordings");
}
}
}
MainWindow xaml和代码背后:
<Window x:Class="StackOverFlowExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverFlowExample"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:RecordingListControl x:Name="_ctrl"></local:RecordingListControl>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_ctrl.SomeDependency = true;
_ctrl.DoubleClickEvent += _ctrl_DoubleClickEvent;
}
void _ctrl_DoubleClickEvent(object sender, RecordingInfo info)
{
MessageBox.Show("You double clicked me!");
}
}
答案 0 :(得分:4)
我不确定你真正想要实现的目标。单击是否仅更改标题,而双击是否可以使用DataGrid-Entry执行某些操作?
如果是这样的话,您可以轻松区分这两者,这样您就不会处理错误的案例(取决于您选择的处理程序):
MouseDown:
OnMouseDownDown(object sender, MouseButtonEventArgs e)
{
UIElement uiElement = (UIElement) sender;
Point hitPoint = e.GetPosition(uiElement);
HitTestResult hitTestResult = VisualTreeHelper.HitTest(uiElement, hitPoint);
if (hitTestResult == null)
{
return;
}
Visual DataGridHeader= hitTestResult.VisualHit.FindParent<DataGridHeader>();
}
单击:使用VisualTreeHelper获取eventArgs.OriginalSource
的[您寻找的任何类型。]的第一个父级。OnHandleMouseClick(object sender, RoutedEventArgs e)
{
var target = e.OriginalSource.FindVisualParent<DataGridHeader>();
}
如果您只想区分单击和双击(可能与hittesting结合使用,您仍然可以使用MouseDown并查看EventArgs中的ClickCount。此计数处理给定时间范围内的点击和可视矩形以区分单个和双击。
希望这有帮助。
EDIT ---------------------------------------------- --------&GT;
我评论后建了一个小样本。您可以在这里下载: http://1drv.ms/1kgnCeQ
当我在StackOverflow上发现另一个问题时,我正在寻找一个解决DispatcherTimer的解决方案。虽然我提出了正确的想法,但您可以找到此处使用的代码的来源:WPF: Button single click + double click issue。
这应该是你要求的。如果您需要有关F2或ContextMenu的帮助,请发表评论。 :)
一切顺利 Gope
EDIT2 ---------------------------------------------- -------&GT;
我已经创建了一个处理您所有要求的新示例:http://1drv.ms/1kgnCeQ 它包含一个简单的ContextMenu,以及除前一个要求之外的F2编辑。它在ExceptionHandling等方面非常原始,但它显示了如何处理所有情况。应该这样做。我还在DoubleClick部件中添加了dataGrid.CancelEdit以重置editmode。 :)
以下是样本中的代码,以便其他人更轻松地阅读此答案:
MainWindow.xaml:
<Window x:Class="IDevign.DataGrids.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataGrids="clr-namespace:IDevign.DataGrids"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" MouseDoubleClick="Button_MouseDoubleClick" PreviewKeyDown="DataGrid_OnPreviewKeyDown">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
</Style>
</DataGrid.Resources>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Click="MenuItem_OnClick"></MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="Recording Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Duration" Binding="{Binding Duration}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private static DispatcherTimer myClickWaitTimer = new DispatcherTimer(
new TimeSpan(0, 0, 0, 1),
DispatcherPriority.Background,
mouseWaitTimer_Tick,
Dispatcher.CurrentDispatcher);
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ObservableCollection<Song> songs = new ObservableCollection<Song>
{
new Song {Duration = 3.11, Name = "Best song ever"},
new Song {Duration = 3.33, Name = "2nd best song ever"},
new Song {Duration = 3.02, Name = "3rd best song ever"}
};
dataGrid.ItemsSource = songs;
}
private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Stop the timer from ticking.
myClickWaitTimer.Stop();
dataGrid.CancelEdit();
MessageBox.Show("DoubleClicked");
e.Handled = true;
}
private static void mouseWaitTimer_Tick(object sender, EventArgs e)
{
myClickWaitTimer.Stop();
}
/// <summary>
/// Handles the LeftButton Click
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
myClickWaitTimer.Start();
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
if (!cell.IsFocused)
{
cell.Focus();
}
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid != null)
{
if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
cell.IsSelected = true;
}
else
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
}
}
}
/// <summary>
/// KeyDown Handler for F2 Key
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataGrid_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (dataGrid.SelectedItem == null || e.Key != Key.F2)
{
return;
}
DataGridRow selectedRow = dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItem) as DataGridRow;
if (selectedRow == null)
{
return;
}
DataGridCell cell = GetCell(dataGrid, selectedRow, 0);
if (cell != null)
{
cell.Focus();
dataGrid.BeginEdit();
e.Handled = true;
}
}
/// <summary>
/// ContextMenu
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
DataGridRow selectedRow = dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItem) as DataGridRow;
if (selectedRow == null)
{
return;
}
DataGridCell cell = GetCell(dataGrid, selectedRow, 0);
if (cell != null)
{
cell.Focus();
dataGrid.BeginEdit();
e.Handled = true;
}
}
/// <summary>
/// Helper Method to get a cell by columnIndex
/// </summary>
/// <param name="dataGrid"></param>
/// <param name="rowContainer"></param>
/// <param name="column"></param>
/// <returns></returns>
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
/* if the row has been virtualized away, call its ApplyTemplate() method
* to build its visual tree in order for the DataGridCellsPresenter
* and the DataGridCells to be created */
rowContainer.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
/* bring the column into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
}
return null;
}
#region VisualTreeHelper Methods
private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T) child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
private static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
T correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
#endregion
}
Song课程的最后一课:
class Song : INotifyPropertyChanged
{
private string name;
private double duration;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
public double Duration
{
get { return duration; }
set
{
duration = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}