I have a WPF form which basically looks like this:
<Window ...>
<Grid>
<DockPanel>
[content shown during normal operation]
</DockPanel>
<Grid Background="#CCCC" Visibility="Hidden">
[overlay grid which is only shown during special circumstances]
</Grid>
</Grid>
</Window>
The overlay grid hides everything else (i.e. the "normal content") and is only shown under special circumstances (i.e. if the network connection goes down). This works perfectly fine when running the program.
Now, in design mode, the problem is that Visual Studio ignores the Visibility="Hidden". Usually, this makes perfect sense (after all, I want to be able to edit the hidden UI elements), but in my case it's annoying, because it prevents me from editing the stuff in the DockPanel in the designer.
So, what I'd like to do is something like that:
<Grid Background="#CCCC" Visibility="Hidden" VS.ShowInDesigner="False">
[overlay grid which is only shown during special circumstances]
</Grid>
But, alas, there is no such property, or at least none that I know of. Any ideas?
Starting from VS2012 you can just use the Blend namespace IsHidden attribute:
add if not already present xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
put d:IsHidden="true" on element you want to hide at design time only
Nice solution, I was having a similar problem and I agree that there are cases where it's needed. Here is a minor update that allows you to edit the value to turn IsHidden on and off while designing. I also applied a ScaleTransform instead of setting Width and Height to reduce screen artifacts a bit if control grips etc are displayed and to avoid conflicts if the control being hidden already has Width and Height properties set (assuming that the control doesn't already have a LayoutTransform set on it).
Public Class DesignModeTool
Public Shared ReadOnly IsHiddenProperty As DependencyProperty = DependencyProperty.RegisterAttached( _
"IsHidden", GetType(Boolean), GetType(DesignModeTool), _
New FrameworkPropertyMetadata(False, New PropertyChangedCallback(AddressOf OnIsHiddenChanged)))
Public Shared Sub SetIsHidden(ByVal element As FrameworkElement, ByVal value As Boolean)
element.SetValue(IsHiddenProperty, value)
End Sub
Public Shared Function GetIsHidden(ByVal element As FrameworkElement) As Boolean
Return DirectCast(element.GetValue(IsHiddenProperty), Boolean)
End Function
Private Shared Sub OnIsHiddenChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If System.ComponentModel.DesignerProperties.GetIsInDesignMode(d) AndAlso True.Equals(e.NewValue) Then
With DirectCast(d, FrameworkElement)
.LayoutTransform = New ScaleTransform(0.001, 0.001)
End With
ElseIf System.ComponentModel.DesignerProperties.GetIsInDesignMode(d) AndAlso False.Equals(e.NewValue) Then
With DirectCast(d, FrameworkElement)
.LayoutTransform = Nothing
End With
End If
End Sub
End Class
Nice work! I translated to C# and change the property it's changing to RenderTransform.
static class DesignModeTool
{
public static readonly DependencyProperty IsHiddenProperty =
DependencyProperty.RegisterAttached("IsHidden",
typeof(bool),
typeof(DesignModeTool),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsHiddenChanged)));
public static void SetIsHidden(FrameworkElement element, bool value)
{
element.SetValue(IsHiddenProperty, value);
}
public static bool GetIsHidden(FrameworkElement element)
{
return (bool)element.GetValue(IsHiddenProperty);
}
private static void OnIsHiddenChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(d)) return;
var element = (FrameworkElement)d;
element.RenderTransform = (bool)e.NewValue
? new ScaleTransform(0, 0)
: null;
}
}
Since there is no built-in way to do this, I decided to implement a solution myself, which was surprisingly easy to do using attached properties:
Public Class DesignModeTool
Public Shared ReadOnly IsHiddenProperty As DependencyProperty = DependencyProperty.RegisterAttached( _
"IsHidden", GetType(Boolean), GetType(DesignModeTool), _
New FrameworkPropertyMetadata(False, New PropertyChangedCallback(AddressOf OnIsHiddenChanged)))
Public Shared Sub SetIsHidden(ByVal element As UIElement, ByVal value As Boolean)
element.SetValue(IsHiddenProperty, value)
End Sub
Public Shared Function GetIsHidden(ByVal element As UIElement) As Boolean
Return DirectCast(element.GetValue(IsHiddenProperty), Boolean)
End Function
Private Shared Sub OnIsHiddenChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If System.ComponentModel.DesignerProperties.GetIsInDesignMode(d) AndAlso True.Equals(e.NewValue) Then
With DirectCast(d, FrameworkElement)
.Width = 0
.Height = 0
End With
End If
End Sub
End Class
After declaring a namespace, the feature can be used like this:
<Grid ... local:DesignModeTool.IsHidden="True">
[stuff I don't want to be shown in the designer]
</Grid>
Other than not using the designer (really, consider this) you could separate the contents of the Grid into a separate UserControl. That way, you could just update that UserControl in isolation from the visibility logic.
I ran into a similar problem recently.
I am using a Rectangle to obscure the main window during a modal dialog's execution. I have the Visibility data bound, but the Rectangle made the designer unusable. I mad the Z index a one time data bind, and a fallback value was lower than the window I wanted to obscure. When the application starts up, the Rectangle's Z index is bound to a higher value than the window.
I am on the other side... hate VS 2012 for hiding hidden WPF controls in designer. I need to see them so i have modified gregsdennis code to:
public class DesignModeTool
{
public static readonly DependencyProperty IsHiddenProperty = DependencyProperty.RegisterAttached("IsHidden", typeof(bool), typeof(DesignModeTool), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsHiddenChanged)));
public static void SetIsHidden(FrameworkElement element, bool value)
{
element.SetValue(IsHiddenProperty, value);
}
public static bool GetIsHidden(FrameworkElement element)
{
return (bool)element.GetValue(IsHiddenProperty);
}
private static void OnIsHiddenChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (!DesignerProperties.GetIsInDesignMode(d)) return;
var element = (FrameworkElement)d;
element.Visibility=Visibility.Visible;
}
}
wpfClasses2:DesignModeTool.IsHidden="False" will show the control in designer mode.
Related
I am trying to achieve the following in a WPF personal finance app:
In various places I want to display a user control giving details of a asset holding (usually a share, bond etc), the target asset may be changed dynamically by the user in which case the control must be refreshed. Each Asset has a unique identifier, AssetId.
I am using MVVM and I've developed a single window with a View Model that takes AssetID as a parameter (property) and retrieves the relevant details for binding to the View. This work fine. What I'd like to do is make a generic user control with the same functionality so I can basically drop that 'window' inside other windows.
So I pretty much copy-pasted the XAML from that form into a User Control, where I'm struggling is passing in the AssetId from the parent window to the child control.
Google tells me I need a dependency property and here's where I am
Public Class HoldingView
Private _AssetId As Integer
Public AssetIdProperty As DependencyProperty = DependencyProperty.Register("AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(New PropertyChangedCallback(AddressOf AssetIDChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Sub AssetIDChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim NewAssetId As Integer
NewAssetId = e.NewValue
Me.DataContext.AssetId = NewAssetId
End Sub
Public Sub New()
' This call is required by the designer.
InitializeComponent()
Me.DataContext = New HoldingViewmodel
End Sub
End Class
Called like this:
<Grid>
<local:HoldingView AssetId="{Binding AssetId}"/>
</Grid>
The code compiles and runs but when I try and load the window that has the user control, the app crashes with this message:
A 'Binding' cannot be set on the 'AssetId' property of type 'HoldingView'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Which is not that helpful. From my Googling, you can also get this message if the syntax of the DP registration is not spot on, but it looks Ok to my inexperienced eye...
Anybody else had this?
Public AssetIdProperty As DependencyProperty
should be
Public Shared ReadOnly AssetIdProperty As DependencyProperty
Please take a look at Custom Dependency Properties.
Also remove
Me.DataContext = New HoldingViewmodel
because that will effectively break any DataContext-based Bindings like
AssetId="{Binding AssetId}"
where the source property is supposed to be owned by the object in the inherited DataContext, which usually is an object in the application's view model.
Controls should never have their own, "private" view model, but instead handle property changes in code behind. In case of UserControls, there could simply be UI elements in their XAML that would be bound to the UserConrol's own properties.
Hence
Me.DataContext.AssetId = NewAssetId
in the PropertyChangedCallback is pointless and should be removed, as well as
Private _AssetId As Integer
To summarize, it should look like this:
Public Class HoldingView
Public Shared ReadOnly AssetIdProperty As DependencyProperty =
DependencyProperty.Register(
"AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(
New PropertyChangedCallback(AddressOf AssetIdPropertyChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Shared Sub AssetIdPropertyChanged(
d As DependencyObject, e As DependencyPropertyChangedEventArgs)
CType(d, HoldingView).AssetIdChanged(e.NewValue)
End Sub
Private Sub AssetIdChanged(id As Integer)
...
End Sub
Public Sub New()
InitializeComponent()
End Sub
End Class
I have written a custom control based on a ListBox. It contains a default ItemTemplate which shows an image given to the ListBox by a custom dependency property. The control also contains a default image, which is used when the user doesn't give an image to the dependency property.
This works so far, but now I've found a little problem and I don't know how to fix that.
When I use my custom control in the XAML designer, it first shows the default image. When I set the image's dependency property to an other image, the new image is immediately shown in the XAML designer.
But when I remove the XAML attribute for the new image again, the XAML designer only shows a white rectangle instead of the default image.
I assume it's because with setting the image's dependency property to some value and then removing it I nulled the value. But even when I check for null in the CoerceCallback and give back the default image when the coerced value is null, doesn't work.
What's the best way to support fallback values for dependency properties?
TestControl.vb
Public Class TestControl
Inherits ListBox
Private Shared _defaultResources As ResourceDictionary
Shared Sub New()
_defaultResources = New ResourceDictionary
_defaultResources.Source = New Uri("...")
End Sub
Public Shared ReadOnly TestProperty As DependencyProperty = DependencyProperty.Register(NameOf(TestControl.Test),
GetType(ImageSource),
GetType(TestControl),
New FrameworkPropertyMetadata(Nothing,
AddressOf TestControl.OnTestChanged,
AddressOf TestControl.OnTestCoerce))
Public Property Test As ImageSource
Get
Return DirectCast(MyBase.GetValue(TestControl.TestProperty), ImageSource)
End Get
Set(value As ImageSource)
MyBase.SetValue(TestControl.TestProperty, value)
End Set
End Property
Private Shared Sub OnTestChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
End Sub
Private Shared Function OnTestCoerce(d As DependencyObject, value As Object) As Object
If (value Is Nothing) Then
Return TryCast(_defaultResources.Item("TestImage"), ImageSource)
End If
Return value
End Function
Public Sub New()
Me.Test = TryCast(_defaultResources.Item("TestImage"), ImageSource)
End Sub
End Class
When I use that control like this
<local:TestControl ItemsSource="{Binding Items}" />
every item shows the default image at design time. When I change the XAML to
<local:TestControl ItemsSource="{Binding Items}"
Test="{StaticResource NewImage}" />
every item shows the new item at design time. But when I remove the Test="{StaticResource NewImage}" again, it doesn't go back to the default image.
Ok, after some testing (using this technique) I have discovered the source of your issue.
First of all, you are not using PropertyMetadata to set your default value, but instead the constructor. I assume you have a good reason to do so, but this essentially means that now you are relying on the coercion callback to set the default value.
However, it is not called (the framework assumes that your "true" default value - Nothing - doesn't need to be validated) after you remove the
Test="{StaticResource TestString}" line. Only the OnTestChanged
is called. This means we can use it to restore the default value:
void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is null)
{
((TestControl)d).Test = yourDefaultImage;
return;
}
//Actual "OnChanged" code
}
A clumsy solution indeed, but it works. Depending on your exact situation, you might also want to take a look at Binding's FallbackValue and TargetNullValue properties:
Test="{Binding Source={ }, FallbackValue={ }, TargetNullValue={ }}"
I have created an Extended TextBox that Inherits a standard WPF TextBox, what I am now trying to do is create other extended control types like a TextBlock, ListBox, ComboBox etc. All controls will have the same DependencyProperties as shown below so I am trying to find a way to implement this without repeating the DependencyProperty code behind each new extended control.
Public Class ExtendedTextBox
Inherits TextBox
Public Shared MandatoryProperty As DependencyProperty = DependencyProperty.Register("Mandatory", GetType(Boolean), GetType(ExtendedTextBox))
Public Shared ReadOnly HasAnyErrorsProperty As DependencyProperty = DependencyProperty.Register("HasAnyErrors", GetType(Boolean), GetType(ExtendedTextBox))
End Class
You could define attached properties that can be set on any UIElement:
Public Class MyProperties
Public Shared ReadOnly MandatoryProperty As DependencyProperty = DependencyProperty.RegisterAttached("Mandatory", GetType(Boolean), GetType(MyProperties))
Public Shared Sub SetMandatory(ByVal element As UIElement, ByVal value As Boolean)
element.SetValue(MandatoryProperty, value)
End Sub
Public Shared Function GetMandatory(ByVal element As UIElement) As Boolean
Return CType(element.GetValue(MandatoryProperty), Boolean)
End Function
End Class
XAML:
<TextBox local:MyProperties.Mandatory="True" />
<ListBox local:MyProperties.Mandatory="False" />
this relates to my earlier question - I want to animate a grid splitter (to make panels slide into / out of view). We are pretty good at VB and already have a VB project, so would like to stay with VB if we can, but most WPF examples seem to be in XAML or CS.
I have some simple VB animation code working, BUT:
Of course, what needs to be animated is the width / height of the grid column / row, and this is not a dependency property. I found some clever stuff in CS to make a dependency property but could not translate this to vb. So I found a simple workaround which is to animate a dockpanel in the grid cell, catch it's size changed events and use these to set the cell grid size. It works but I wonder if it's less efficient as 2 things are being changed separately? Also I have to (when the animation completes) set the grid cells sizes back to * in the right proportion, and the dockpanel size back to auto.
It works, but it seems a bit clumsy - does someone have an example of making the animation for the grid work directly from VB any any other suggestions?
Thanks
For reference, here is the VB code for a dependency property to animate a gridsplitter:
Public Class GridLengthAnimation
Inherits AnimationTimeline
Public Sub New()
End Sub
Public Property From() As GridLength
Get
Return DirectCast(GetValue(FromProperty), GridLength)
End Get
Set(value As GridLength)
SetValue(FromProperty, value)
End Set
End Property
Public Shared ReadOnly FromProperty As DependencyProperty
= DependencyProperty.Register("From", GetType(GridLength),
GetType(GridLengthAnimation))
Public Property [To]() As GridLength
Get
Return DirectCast(GetValue(ToProperty), GridLength)
End Get
Set(value As GridLength)
SetValue(ToProperty, value)
End Set
End Property
Public Shared ReadOnly ToProperty As DependencyProperty
= DependencyProperty.Register("To", GetType(GridLength),
GetType(GridLengthAnimation))
Public Overrides ReadOnly Property TargetPropertyType() As Type
Get
Return GetType(GridLength)
End Get
End Property
Protected Overrides Function CreateInstanceCore() As Freezable
Return New GridLengthAnimation()
End Function
Public Overrides Function GetCurrentValue
(defaultOriginValue As Object,
defaultDestinationValue As Object,
animationClock As AnimationClock) As Object
Dim fromValue As Double = Me.From.Value
Dim toValue As Double = Me.[To].Value
If fromValue > toValue Then
Return New GridLength((1 - animationClock.CurrentProgress.Value)
* (fromValue - toValue) + toValue,
If(Me.[To].IsStar, GridUnitType.Star, GridUnitType.Pixel))
Else
Return New GridLength((animationClock.CurrentProgress.Value) *
(toValue - fromValue) + fromValue,
If(Me.[To].IsStar, GridUnitType.Star, GridUnitType.Pixel))
End If
End Function
End Class
I'm trying to get the WebBrowser control to display HTML loaded from a variable. I found this solution Displaying html from string in WPF WebBrowser control so am trying to implement my first DependencyProperty.
Here's my attempt at the implementation:
Public Shared ReadOnly HtmlProperty As DependencyProperty = DependencyProperty.RegisterAttached("Html", GetType(String), GetType(PortraitSingle), New FrameworkPropertyMetadata(OnHtmlChanged))
<AttachedPropertyBrowsableForType(GetType(WebBrowser))> _
Public Shared Function GetHtml(ByVal d As WebBrowser) As String
Return DirectCast(d.GetValue(HtmlProperty), String)
End Function
Public Shared Sub SetHtml(ByVal d As WebBrowser, ByVal value As String)
d.SetValue(HtmlProperty, value)
End Sub
Private Shared Sub OnHtmlChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim wb As WebBrowser = TryCast(d, WebBrowser)
If wb IsNot Nothing Then
wb.NavigateToString(TryCast(e.NewValue, String))
End If
End Sub
and the XAML
<WebBrowser x:Name="HTMLDescription" Grid.RowSpan="2" Margin="10" local:PortraitSingle.Html="{Binding HtmlToDisplay}"></WebBrowser>
First off, the problem I'm having is I'm getting the following errors:
Argument not specified for parameter 'e' of 'Private Shared Sub OnHtmlChanged(d As System.Windows.DependencyObject, e As System.Windows.DependencyPropertyChangedEventArgs)
and
The attachable property 'Html' was not found in type 'PortraitSingle'. (PortraitSingle is the name of my Window).
I can't work out how to get around them.
Also, I can't understand how I actually load my HTML into the WebBrowser control.
Ben
Found my problem.
Needed to add the 'AddressOf' to the DependencyProperty statement
Public Shared ReadOnly HtmlProperty As DependencyProperty = DependencyProperty.RegisterAttached("Html", GetType(String), GetType(PortraitSingle), New FrameworkPropertyMetadata(AddressOf OnHtmlChanged))