У меня есть пользовательский объект, который я пытаюсь привязать к элементу управления. В этом пользовательском объекте я реализовал интерфейс INotifyPropertyChanged. Я успешно связал свой объект и свойство этого объекта.
Что я не могу понять, так это как оттуда идти. Я работаю над этим уже 2 дня и до сих пор не могу заставить его работать.
Я предполагал, что когда я изменю свойство, привязанное к элементу управления, значение, установленное в этом свойстве, будет отображаться в элементе управления. Тем не менее, независимо от того, насколько я изменяю свойство, пользовательский интерфейс никогда не обновляется сверх его начального значения.
Я реализовал INotifyPropertyChanged следующим образом:
Базовый класс, который реализует INotifyPropertyChanged
Итак, мой базовый класс такой:
[Serializable]
public abstract class BindableObject : INotifyPropertyChanged
{
#region Data
private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
private const string ERROR_MSG = "{0} is not a public property of {1}";
#endregion // Data
#region Constructors
static BindableObject()
{
eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
}
protected BindableObject()
{
}
#endregion // Constructors
#region Public Members
/// <summary>
/// Raised when a public property of this object is set.
/// </summary>
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Returns an instance of PropertyChangedEventArgs for
/// the specified property name.
/// </summary>
/// <param name="propertyName">
/// The name of the property to create event args for.
/// </param>
public static PropertyChangedEventArgs
GetPropertyChangedEventArgs(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentException(
"propertyName cannot be null or empty.");
PropertyChangedEventArgs args;
// Get the event args from the cache, creating them
// and adding to the cache if necessary.
lock (typeof(BindableObject))
{
bool isCached = eventArgCache.ContainsKey(propertyName);
if (!isCached)
{
eventArgCache.Add(
propertyName,
new PropertyChangedEventArgs(propertyName));
}
args = eventArgCache[propertyName];
}
return args;
}
#endregion // Public Members
#region Protected Members
/// <summary>
/// Derived classes can override this method to
/// execute logic after a property is set. The
/// base implementation does nothing.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected virtual void AfterPropertyChanged(string propertyName)
{
}
/// <summary>
/// Attempts to raise the PropertyChanged event, and
/// invokes the virtual AfterPropertyChanged method,
/// regardless of whether the event was raised or not.
/// </summary>
/// <param name="propertyName">
/// The property which was changed.
/// </param>
protected void RaisePropertyChanged(string propertyName)
{
this.VerifyProperty(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
// Get the cached event args.
PropertyChangedEventArgs args =
GetPropertyChangedEventArgs(propertyName);
// Raise the PropertyChanged event.
handler(this, args);
}
this.AfterPropertyChanged(propertyName);
}
#endregion // Protected Members
#region Private Helpers
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = this.GetType();
// Look for a public property with the specified name.
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
// The property could not be found,
// so alert the developer of the problem.
string msg = string.Format(
ERROR_MSG,
propertyName,
type.FullName);
Debug.Fail(msg);
}
}
#endregion // Private Helpers
}
Я наследую от этого класса выше, и в своем производном классе я делаю это на своем свойстве:
public virtual string Name
{
get
{
return m_strName;
}
set
{
m_strName = value;
RaisePropertyChanged("Name");
}
}
Мой XAML выглядит следующим образом (сокращенная версия):
<Window x:Class="PSSPECApplication.Windows.Project"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
DataContext="{Binding SizingProject, RelativeSource={RelativeSource Self}}">
<StackPanel VerticalAlignment="Center">
<TextBox Name="txtProjectName" Text="{Binding Name}" />
</StackPanel>
Вы можете видеть, что контекст данных окна является свойством, называемым SizingProject. SizingProject имеет производный тип (производный от BindableObject), имеет свойство Name и вызывает обработчик события PropertyChanged.
В конструкторе моего окна я заполняю SizingProject, и его свойство Name установлено.
Чтобы проверить это, у меня также есть кнопка в окне, которая запускает событие, которое устанавливает свойство Name в нечто иное, чем оно было изначально. Однако, когда свойство name изменяется, ничего не происходит. Я проследил до BindableObject и событие PropertyChanged всегда имеет значение null, поэтому никакой обработчик никогда не устанавливается и не запускается. Почему это?
Я подумал, реализовав INotifyPropertyChanged и используя этот тип в привязке, вынуждает WPF автоматически устанавливать этот обработчик событий, а затем происходит правильное поведение? Для меня я никогда не вижу такого поведения.
Я понял проблему. Что мне нужно было сделать, это создать DependencyProperty для моего свойства SizingProject. После того, как я это сделал, все заработало нормально.
public static readonly DependencyProperty SizingProjectProperty =
DependencyProperty.Register("SizingProject", typeof(Sizing.Project), typeof(Project), new UIPropertyMetadata());
public Sizing.Project SizingProject
{
get
{
return (Sizing.Project)GetValue(Project.SizingProjectProperty);
}
set
{
SetValue(Project.SizingProjectProperty, value);
}
}