I have a simple custom control derived from TextBox
and used it like that:
<c:MyTextBox Text="Hello World" />
<c:MyTextBox Text="{Binding TextInViewModel}"/>
The first usage works. The one with the databinding doesn't! The design view process (XDesProc.exe) crashes and comes up with:
System.NullReferenceException
Object reference not set to an instance of an object.
at System.Windows.Controls.TextBoxView.System.Windows.Documents.ITextView.Validate(ITextPointer position)
at System.Windows.Documents.TextPointer.System.Windows.Documents.ITextPointer.ValidateLayout()
at System.Windows.Documents.TextSelection.System.Windows.Documents.ITextRange.NotifyChanged(Boolean disableScroll, Boolean skipEvents)
at System.Windows.Documents.TextRangeBase.EndChange(ITextRange thisRange, Boolean disableScroll, Boolean skipEvents)
at System.Windows.Documents.TextRange.System.Windows.Documents.ITextRange.EndChange(Boolean disableScroll, Boolean skipEvents)
at System.Windows.Documents.TextRange.ChangeBlock.System.IDisposable.Dispose()
at System.Windows.Controls.TextBox.OnTextPropertyChanged(String oldText, String newText)
at System.Windows.Controls.TextBox.OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.Controls.TextBox.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
at System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
at System.Windows.Data.BindingExpression.Activate(Object item)
at System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt)
at System.Windows.Data.BindingExpression.MS.Internal.Data.IDataBindEngineClient.AttachToContext(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Task.Run(Boolean lastChance)
at MS.Internal.Data.DataBindEngine.Run(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.DesignerProcess.RunApplication()
at Microsoft.VisualStudio.DesignTools.DesignerContract.Isolation.DesignerProcess.<>c__DisplayClass5_0.<Main>b__0()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Even if the bound property is a stupid auto property like the following, it doesn't work:
public class MyViewModel
{
public string TextInViewModel { get; set; } = "Simple Property";
}
Debugging XDesProc.exe didn't help. Any other hints?
UPDATE (the complete story):
I implemented a MyBorder
control to provide a label for several custom controls like MyTextBox
:
<c:MyTextBox Text="Static Text" c:MyBorder.Label="It works :-)" />
<c:MyTextBox Text="{Binding TextInViewModel}" c:MyBorder.Label="It works not :-(" />
How it should look like at design time (at runtime it works perfectly!):
MyTextBox.cs
:
public class MyTextBox : TextBox
{
static MyTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(typeof(MyTextBox)));
}
}
MyTextBox.xaml
merged into Generic.xaml:
<Style TargetType="{x:Type c:MyTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MyTextBox}">
<c:MyBorder x:Name="Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
IsEnabled="{TemplateBinding IsEnabled}"
Label="{Binding (c:MyBorder.Label), RelativeSource={RelativeSource TemplatedParent}}">
<ScrollViewer x:Name="PART_ContentHost" Padding="0" />
</c:MyBorder>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MyBorder.cs
:
[ContentProperty(nameof(Content))]
public class MyBorder : Control
{
static MyBorder()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyBorder), new FrameworkPropertyMetadata(typeof(MyBorder)));
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(UIElement), typeof(MyBorder), new PropertyMetadata(default(UIElement)));
public UIElement Content
{
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(MyBorder), new PropertyMetadata(string.Empty) { CoerceValueCallback = CoerceString });
public static object CoerceString(DependencyObject d, object value)
{
return value ?? string.Empty;
}
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static void SetLabel(DependencyObject element, string value)
{
element.SetValue(LabelProperty, value);
}
public static string GetLabel(DependencyObject element)
{
return (string)element.GetValue(LabelProperty);
}
}
MyBorder.xaml
merged into Generic.xaml:
<Style TargetType="{x:Type c:MyBorder}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MyBorder}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Label. -->
<Border Padding="3,0" Margin="0,-16,0,0" HorizontalAlignment="Right"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="White">
<TextBlock Foreground="Black" Text="{TemplateBinding Label}" />
</Border>
<!-- Content inside Border. -->
<Border Grid.Row="1"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Stretch" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>