1

I've created a style for a new WPF Window class and have some dependency properties on there. The one to note is

ShowHelpButton

This is supposed to toggle the visibility of the Help button on the window. The code works fine in runtime, but I cannot get it to update the UI in the design view.

Here's the class:

public class MainWindowFrame : Window
{
  #region DependencyProperties

  public static readonly DependencyProperty ShowHelpButtonProperty = DependencyProperty.Register(
     "ShowHelpButton", typeof (bool), typeof (MainWindowFrame), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender));

  public bool ShowHelpButton
  {
     get { return (bool) GetValue(ShowHelpButtonProperty); }
     set { SetValue(ShowHelpButtonProperty, value); }
  }

  #endregion


  static MainWindowFrame()
  {
     DefaultStyleKeyProperty.OverrideMetadata(typeof(MainWindowFrame),
         new FrameworkPropertyMetadata(typeof(MainWindowFrame)));
  }

Here's the Style:

<Style x:Key="MainWindowStyle" TargetType="{x:Type abstractClasses:MainWindowFrame}">
  <Setter Property="HorizontalAlignment" Value="Stretch" />
  <Setter Property="VerticalAlignment" Value="Stretch" />
  <Setter Property="AllowsTransparency" Value="True" />
  <Setter Property="Background" Value="{StaticResource LightBlueBrush}" />
  <Setter Property="BorderBrush" Value="{StaticResource BlueBrush}" />
  <Setter Property="BorderThickness" Value="1" />
  <Setter Property="CornerRadius" Value="1" />
  <Setter Property="ResizeMode" Value="NoResize" />
  <Setter Property="WindowStyle" Value="None" />
  <Setter Property="Title" Value="New Window" />
  <Setter Property="Template">
     <Setter.Value>
        <ControlTemplate TargetType="{x:Type abstractClasses:MainWindowFrame}">
           <Border
              Background="{TemplateBinding Background}"
              BorderBrush="{TemplateBinding BorderBrush}"
              BorderThickness="{TemplateBinding BorderThickness}"
              CornerRadius="{TemplateBinding CornerRadius}">
              <Grid x:Name="ContainerGrid" Background="Transparent">
                 <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                 </Grid.RowDefinitions>
                 <Grid.Triggers>
                    <EventTrigger RoutedEvent="Grid.Loaded">
                       <BeginStoryboard>
                          <Storyboard>
                             <DoubleAnimation
                                Storyboard.TargetProperty="Opacity"
                                From="0"
                                To="1"
                                Duration="00:00:01" />
                          </Storyboard>
                       </BeginStoryboard>
                    </EventTrigger>
                 </Grid.Triggers>
                 <Grid Background="Transparent" MouseDown="Window_MouseDownDrag">
                    <Grid.ColumnDefinitions>
                       <ColumnDefinition Width="*" />
                       <ColumnDefinition Width="Auto" />
                       <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid Grid.Column="0">
                       <TextBlock
                          Margin="10,3,0,3"
                          HorizontalAlignment="Left"
                          VerticalAlignment="Center"
                          Style="{StaticResource CustomTitleBarTextBlackB}"
                          Text="{TemplateBinding Title}" />
                    </Grid>
                    <Button
                       Grid.Column="1"
                       Width="20"
                       Height="20"
                       Margin="0,0,5,0"
                       HorizontalAlignment="Right"
                       AutomationProperties.AutomationId="Help"
                       Style="{StaticResource HelpButtonStyle}"
                       Visibility="{TemplateBinding Property=ShowHelpButton,
                                                    Converter={StaticResource BoolToVisConverter}}" />
                 </Grid>

                 <AdornerDecorator Grid.Row="1">
                    <ContentPresenter x:Name="WindowContent" />
                 </AdornerDecorator>
              </Grid>
           </Border>
        </ControlTemplate>
     </Setter.Value>
  </Setter>

And finally, here's how I'm using it:

<abstractClasses:MainWindowFrame
x:Class="Utils.UI.NewFeaturesDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:abstractClasses="clr-namespace:Utils.AbstractClasses"
xmlns:ui="clr-namespace:Utils.UI"
xmlns:utilResx="clr-namespace:Utils.Resources"
Width="775"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ShowHelpButton="False"
SizeToContent="Height"
Style="{DynamicResource ResourceKey=MainWindowStyle}">

<Window.Resources>
   <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
         <ResourceDictionary Source="/Utils;component/WPFStyles/Styles.xaml"/>
     </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Window.Resources>
</abstractClasses:MainWindowFrame>

I've seemingly tried everything. I've added all the FrameworkPropertyMetadataOptions by doing this:

FrameworkPropertyMetadataOptions.AffectsArrange |

FrameworkPropertyMetadataOptions.AffectsMeasure |

FrameworkPropertyMetadataOptions.AffectsRender |

FrameworkPropertyMetadataOptions.AffectsParentMeasure |

FrameworkPropertyMetadataOptions.AffectsParentArrange

I've also added a callback to no avail. I've even tried restarting Visual Studio 2015. I'm starting to think it's just a VS bug, but I'm hoping someone has some idea of what's going on. Thanks for any help!

Just Ask
  • 329
  • 2
  • 6
  • 22

3 Answers3

3

Updated answer

It looks like its a known design-time bug; for subclassed/derived Window objects This reported bug seems to be related to this issue: WPF designer not showing content assigned to custom DependencyProperty

We cannot create a design instance of Window within the designer so we substitute with a proxy type of our own.

So if the designer can't create an instance of derived window type; the binding (TemplateBinding) logic will fail during design-time.

As it is not easy to provide fallback values to TemplateBinding, you can maybe use this approach to provide default values to act as design-time behavior.

Community
  • 1
  • 1
Sharada Gururaj
  • 13,471
  • 1
  • 22
  • 50
  • 1
    This has nothing to do with DataContext. TemplateBinding is exactly for this – Liero Apr 23 '17 at 14:17
  • Agreed. Just went through your code again; I had missed the template binding. I will update my answer. – Sharada Gururaj Apr 23 '17 at 14:43
  • That's what I thought (and was afraid of haha). Thanks for confirming that it's a VS bug. I hope it's not actually the intended design as that one comment suggests. Maybe one day it'll get fixed. – Just Ask Apr 24 '17 at 18:20
0

Sorry I can't recreate your problem.

I go usually in a simpler but still complicated way. I put ViewModel in another assembly, so I would have no temptation to reference it from view. + To make everything stylable, I put Templates into Generic.xaml and override Styles in another dictionary, that is loaded after Generic in App.xaml Here is how I do things.

MyWindow.xaml:

<Window x:Class="Sandbox.MyWindow"
    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:Sandbox"
    xmlns:l="clr-namespace:ProjectLibrary;assembly=ProjectLibrary"
    mc:Ignorable="d" d:DataContext="{DynamicResource DesignViewModel}"
    Title="MyWindow" Height="300" Width="300">
<Window.Resources>
    <l:MyViewModel x:Key="DesignViewModel" SomeButtonVisibility="Collapsed"/>
</Window.Resources>
<StackPanel>
    <TextBlock Text="{Binding SomeText}"/>
    <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="1 Button"/>
    <Button Visibility="{Binding SomeButtonVisibility}" HorizontalAlignment="Center" VerticalAlignment="Center" Content="2 Button"/>
    <Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="3 Button"/>
</StackPanel>

In App.xaml I remove StartupURI to handle it in App.xaml.cs:

using ProjectLibrary;
using System.Windows;

namespace Sandbox {
public partial class App : Application {
    MyWindow w;
    MyViewModel vm;

    public App() {
        w = new MyWindow();
        //You also can pass Action to open new window of some sort here
        //or other things, that VM can't have access to
        vm = new MyViewModel(true);
        w.DataContext = vm;

        w.Show();
    }
  }
}

MyViewModel:

using System.Windows;

namespace ProjectLibrary
{
public class MyViewModel : Notifiable
{
    public MyViewModel() :this(false) {
    }

    public MyViewModel(bool Execute) {
        if (Execute) {
            SomeText = "Execution data";
        } else {
            SomeText = "Design Data";
        }
        SomeButtonVisibility = Visibility.Visible;
    }

    private string _someText;
    public string SomeText { get { return _someText; } set { _someText = value; RaisePropertyChanged("SomeText"); } }

    private Visibility _someButtonVisibility;
    public Visibility SomeButtonVisibility { get { return _someButtonVisibility; } set { _someButtonVisibility = value; RaisePropertyChanged("SomeButtonVisibility"); } }
 }
}

Notifiable.cs:

using System.ComponentModel;

namespace PTR.PTRLib.Common {
public class Notifiable : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName) {
        // take a copy to prevent thread issues
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
 }
}

App.xaml:

<Application x:Class="Sandbox.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:Sandbox">
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Themes/Generic.xaml"/> <!-- Default Styles -->
            <ResourceDictionary Source="Themes/StyleRes.xaml"/> <!-- ColorTemplates -->
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Templates/Generic.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:Sandbox">
<Style TargetType="{x:Type local:MyWindow}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Grid>
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <AdornerDecorator>
                            <ContentPresenter/>
                        </AdornerDecorator>
                    </Border>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="ResizeMode" Value="CanResizeWithGrip">
            <Setter Property="Template" Value="{StaticResource WindowTemplateKey}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Themes/StyleRes.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:Sandbox">

<Style TargetType="{x:Type local:MyWindow}">
    <Setter Property="Foreground" Value="Red"/>
    <Setter Property="Background" Value="LightBlue"/>
</Style>
<Style TargetType="{x:Type Button}">
    <Setter Property="Foreground" Value="Red"/>
    <Setter Property="Background" Value="LightBlue"/>
</Style>

I'm not a professional in WPF, but this is what I learned in 1.5 year course of programming a database + wpf interface (nice one).

zORg Alex
  • 372
  • 2
  • 15
  • Aww... As Sharada Gururaj said it's a known bug. Now I remember that I dealt with something like that previously and found no solution. So I started doing everything in user controls, but now I realized that you can do CustomControls easily with my pattern. You can prototype every control as UserControl and then copy/paste and create a CustomControl. It will be easy to make templates and reuse your controls later. – zORg Alex Apr 24 '17 at 12:02
  • Thanks for your reply! This could possibly work as a workaround, but the only issue is that all the code to design MyWindow has to be done in a .cs file since I'd want to inherit MyWindow in another class that includes xaml code (you aren't allowed to inherit a class that was defined using xaml in another xaml file). I don't really want to create all my UI code in cs files. – Just Ask Apr 24 '17 at 18:16
0

I know this may be a short answer.... But I am thinking the issue, since it is only at design time, might be due to a data context issues in the designer. Have you tried d:DataContext?

 d:DataContext ="{d:DesignInstance {x:Type nameSpace:ViewModel}, IsDesignTimeCreatable=True}"

https://www.codeproject.com/tips/879109/using-design-time-databinding-while-developing-a-w

Toskr
  • 379
  • 2
  • 7