what I want to do is to give the user the ability to change some (here one) property of all text boxes in my application. When the user triggers my event, every textbox in every usercontrol, panel etc. is ought to be changed.
For Example all textboxes should change to Multiline=true; (I know this doesn't make much sense, but my needs a really similar to this), but how to achieve this without looping over every control?
I know I could do something like
foreach(Control item in FindForm().Controls)
{
if(item is TextBox)
{
(item as TextBox).Multiline=true;
}
}
but I don't think that this is a perfect nor a good solution.
I know I could write the settings to a file and read them when the app is starting, but how to change the properties while running the application?
My main problem know is that the ControlProperties don't let me give them a reference to a boolean object, so I can't easily change it in a "settings-object", or do I miss here something?
I don't know of any good tutorials to walk you through it but you can do a DataBinding to any property (including Multiline) not just the text one. This should do what you need to do.
this.txtField.DataBindings.Add(
new System.Windows.Forms.Binding("Multiline",
global::ProjectNamespace.Properties.Settings.Default,
"BoolianSettingInConfigFile",
true,
System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
I used the config file in this example but it can be stored anywhere.
In these circumstances you have two basic options: push the changes to the control (as you sample code does) or have the controls themselves pull the data from the configuration.
Using a pull approach will allow you to update controls at runtime.
You could use databinding on all of your textboxes to bind the 'Multiline' property of the textboxes to some central store of the setting. You could go further and derive custom textbox controls that automatically handle their own databinding setup on instantiation, so once you have replaced the textboxes with your own textbox type (this can actually be done with a search and replace in the code) you don't have to make any more code changes.
Maybe you could just use a data trigger and a globally accessible utility class:
Here's an example where at the click of a button all textblock's will have a red foreground
<Window x:Class="RoomUnit.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:RoomUnit" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<c:Utility x:Key="utility" />
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource utility},
Path=IsRed}" Value="true">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="TEST" />
<Button Content="Make Red" Grid.Row="1" Click="MakeRed" />
</Grid>
heres the utility class
public class Utility : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isRed;
public bool IsRed
{
get { return isRed; }
set
{
isRed = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsRed"));
}
}
}
and heres my button eventhandler
private void MakeRed(object sender, RoutedEventArgs e)
{
var u = (Utility) this.FindResource("utility");
u.IsRed = true;
}
Related
I am trying to implement a control to inherit from in WPF.
I have never been working with WPF (at least at that level though).
So I need some direction of best practice on how to solve this.
The problem I´m facing is that my control, that I want to inherit from, has some child controls that need to be accessed inside the controls base class.
I want to reuse that control with these child controls inside, because it has functions to fill the child controls from outside.
But since WPF can´t inherit a control with xaml, I can´t get my head around a solution.
Let´s say I have this control.
<StackPanel x:Class="Framework.UI.Controls.Base.Navigator.NavigatorItem"
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:Framework.UI.Controls.Base.Navigator"
mc:Ignorable="d"
d:DesignHeight="26" d:DesignWidth="200">
<Button Name="btnHeader" Content="Button"/>
<TreeView Name="tvNavContent" Height="0"/>
</StackPanel>
In codebehind the Button is being used for a Click event as well as the header Text, which I want to be filled from the Control that inherits from this.
And with a function the TreeView "tvNavContent" is being filled with something like this:
<TreeViewItem x:Class="Framework.UI.Controls.Base.Navigator.NavigatorEntry"
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:Framework.UI.Controls.Base.Navigator"
mc:Ignorable="d"
d:DesignHeight="20" d:DesignWidth="200">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Name="imgIcon" Width="16" Height="16" Stretch="Fill"/>
<TextBlock Name="txtTitle"/>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
What I want to achieve is to reuse the Stackpanel with the Button and TreeView inside and with it´s functions.
I tried two things:
First I tried to create a template and applied that to the base class. After that I just tried to load the controls of the template in the base class with the FindName<>() function.
The problem here is, that the template is applied after InitializeComponent().
But during InitializeComponent() I already need access, to set the controls header property for the title from the control that inherits from the base class.
After that I tried to implement the child controls completely in the base class of the control.
Just created them in the constructor and added them to the Children Property of the stackpanel the base class inherits from.
That did (somewhat) work.
But apparently the controls behave completely different when created like that.
No matter the settings. I just couldn´t get the controls to fit correctly inside their parents.
Furthermore, this method is completely unsuitable for a larger project, when it comes to theme adjustments.
Can someone guide me in the correct direction here?
Create a class called NavigatorItem (without any .xaml file):
public class NavigatorItem : Control
{
static NavigatorItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavigatorItem),
new FrameworkPropertyMetadata(typeof(NavigatorItem)));
}
}
Create a ResourceDictionary called generic.xaml and put it in a folder called themes (these names are by convention) at the root of your project, and define a default template for the NavigatorItem class in there:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp12">
<Style TargetType="local:NavigatorItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NavigatorItem">
<StackPanel>
<Button Name="btnHeader" Content="Button"/>
<TreeView Name="tvNavContent" Height="0"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
You can then override the OnApplyTemplate of the NavigatorItem class to get a reference to the elements in the template and hook up event handlers to them, e.g.:
public override void OnApplyTemplate()
{
Button button = GetTemplateChild("btnHeader") as Button;
button.Click += Button_Click;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("button clicked!");
}
I have a custom control (MediaPlayer) that contains 2 other custom controls, a media player (Host) and a control bar (UI).
This control in itself is quite simple, it just binds the two together for display.
Now the first problem I got is that I couldn't set Host or UI properties from MediaPlayer, so I duplicated all properties relevant at design-time and linked them via binding. Is this the right away of achieving this? It's kind of clunky but it works.
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid x:Name="PART_HostGrid" Margin="0,0,0,46">
<!--Filled by SetPlayerHost -->
</Grid>
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is a class for a generic media player. Then I have another Custom Control deriving from it that sets it to use a specific media player. (have one using MPV video player, and another one displaying a VapourSynth script output)
The derived class looks like this.
<Style TargetType="{x:Type local:VsMediaPlayer}" BasedOn="{StaticResource {x:Type ui:MediaPlayerWpf}}" />
Now the problem is I want to expose Script and Path properties as dependency properties so they can be databound. I can't take exactly the same approach as above, so how can I do it? The Host the Path and Script will be bound to is created at run-time within OnApplyTemplate.
I'm a bit confused about how to make this one work, and I'm not sure the first code above is the best solution. Thanks for any guidance.
I guess one option is to copy the base style template instead of inheriting from it, and I could initiate the Host class there instead of at run-time. Any other option?
Edit: Host property is declared like this in my generic MediaPlayer class, but I couldn't find a way to set its sub-properties (Host.Source) from the designer.
public static DependencyProperty HostProperty = DependencyProperty.Register("Host", typeof(PlayerBase), typeof(MediaPlayerWpf),
new PropertyMetadata(null, OnHostChanged));
public PlayerBase Host { get => (PlayerBase)GetValue(HostProperty); set => SetValue(HostProperty, value); }
private static void OnHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (e.OldValue != null)
P.HostGrid.Children.Remove(e.OldValue as PlayerBase);
if (e.NewValue != null) {
P.HostGrid.Children.Add(e.NewValue as PlayerBase);
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
}
Edit: this is the XAML code of MediaPlayer
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EmergenceGuardian.MediaPlayerUI">
<Style TargetType="{x:Type local:MediaPlayerWpf}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MediaPlayerWpf}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46"
Content="{TemplateBinding Content}" />
<local:PlayerControls x:Name="PART_MediaUI" Height="46" Width="Auto"
VerticalAlignment="Bottom" MouseFullscreen="{TemplateBinding MouseFullscreen}"
MousePause="{TemplateBinding MousePause}"
IsPlayPauseVisible="{Binding IsPlayPauseVisible, RelativeSource={RelativeSource TemplatedParent}}"
IsStopVisible="{TemplateBinding IsStopVisible}"
IsLoopVisible="{TemplateBinding IsLoopVisible}"
IsVolumeVisible="{TemplateBinding IsVolumeVisible}"
IsSpeedVisible="{TemplateBinding IsSpeedVisible}"
IsSeekBarVisible="{TemplateBinding IsSeekBarVisible}"
PositionDisplay="{TemplateBinding PositionDisplay}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Adding x:FieldModifier="public" to PART_MediaUI throws "The attribute FieldModifier does not exist in namespace"
SOLUTION!!! After working with a few attached properties, I finally understand how they work, and attached properties are indeed the right solution. This will allow me to set UIProperties.IsVolumeVisible on the parent class. I just need to repeat that code for every property.
public static class UIProperties {
// IsVolumeVisible
public static readonly DependencyProperty IsVolumeVisibleProperty = DependencyProperty.RegisterAttached("IsVolumeVisible", typeof(bool),
typeof(UIProperties), new UIPropertyMetadata(false, OnIsVolumeVisibleChanged));
public static bool GetIsVolumeVisible(DependencyObject obj) => (bool)obj.GetValue(IsVolumeVisibleProperty);
public static void SetIsVolumeVisible(DependencyObject obj, bool value) => obj.SetValue(IsVolumeVisibleProperty, value);
private static void OnIsVolumeVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (!(d is MediaPlayerWpf P))
return;
P.UI.IsVolumeVisible = (bool)e.NewValue;
}
}
I found a partial solution. Instead of inheriting MediaPlayer from Control, I inherit from ContentControl.
In MediaPlayer Generic.xaml, I display the content like this right above the UI controls
<ContentPresenter x:Name="PART_HostGrid" Margin="0,0,0,46" Content="{TemplateBinding Content}" />
Override property metadata to ensure content is of type PlayerBase and to pass the content reference to the UI control
static MediaPlayerWpf() {
ContentProperty.OverrideMetadata(typeof(MediaPlayerWpf), new FrameworkPropertyMetadata(ContentChanged, CoerceContent));
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
UI = TemplateUI;
UI.PlayerHost = Content as PlayerBase;
}
private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
MediaPlayerWpf P = d as MediaPlayerWpf;
if (P.TemplateUI != null)
P.TemplateUI.PlayerHost = e.NewValue as PlayerBase;
}
private static object CoerceContent(DependencyObject d, object baseValue) {
return baseValue as PlayerBase;
}
And then I can use it like this
<MediaPlayerUI:MediaPlayerWpf x:Name="Player" IsVolumeVisible="False" IsSpeedVisible="False" IsLoopVisible="False" PositionDisplay="Seconds">
<VapourSynthUI:VsMediaPlayerHost x:Name="PlayerHost" />
</MediaPlayerUI:MediaPlayerWpf>
The advantage is that I no longer need to inherit from MediaPlayerWpf so there are less controls to manage.
However, I still need to duplicate UI properties to expose them to the designer, haven't found a way to access them in any other way.
Setting x:FieldModifier="public" in Generic.xaml results in "The attribute 'FieldModifier' does not exist in XML namespace"
UI is exposed as a dependency property like this. The designer allows to set UI="..." but not UI.IsVolumeVisible="false" nor recognizes < local:UI>. Is there a way to expose it from within a custom control?
public static DependencyPropertyKey UIPropertyKey = DependencyProperty.RegisterReadOnly("UI", typeof(PlayerControls), typeof(MediaPlayerWpf), new PropertyMetadata());
public static DependencyProperty UIProperty = UIPropertyKey.DependencyProperty;
public PlayerControls UI { get => (PlayerControls)GetValue(UIProperty); private set => SetValue(UIPropertyKey, value); }
I gave a comment above about how you could use a DependencyProperty and set it that type etc. This is all good but may be overkill for what you need. Just use the x:FieldModifier="public" to get what you're looking for.
Here's an example:
I make 3 user controls and have my MainWindow. The user controls are MainControl, SubControlA, SubControlB.
In MainControl I first give the controls a logical name and then FieldModifier to public.
<UserControl x:Class="Question_Answer_WPF_App.MainControl"
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:Question_Answer_WPF_App"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel>
<local:SubControlA x:Name="SubControlA" x:FieldModifier="public"/>
<local:SubControlB x:Name="SubControlB" x:FieldModifier="public"/>
</StackPanel>
</UserControl>
Then I place that MainControl in my MainWindow and use it like so:
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<local:MainControl>
<local:SubControlA>
<TextBlock Text="I'm in SubControlA" />
</local:SubControlA>
</local:MainControl>
</Grid>
</Window>
Hope this helps. The idea is you can also reference the DependencyPropertys from those controls also like Visibility etc. (or whatever you were using in yours in the question.)
This is just an example as I wouldn't recommend doing it this cheap.
Ok, to follow up the answer from your comments / questions below let me explain how it works a little deeper. First, the SubControlA and SubControlB are just two empty UserControls that I made to have the example work.
In xaml anything between brackets gets initialized at that point. We use the namespace / type name to target the property and whatever is between the brackets goes to the setter of that property.
Consider this MainWindow... All I do is place a custom UserControl in it and it looks like this in xaml
<Window x:Class="Question_Answer_WPF_App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Question_Answer_WPF_App"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<local:ExampleControl />
</Window>
… and it looks like this in when ran
Now to see the custom ExampleControl because so far no big deal.
<UserControl x:Class="Question_Answer_WPF_App.ExampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<StackPanel>
<Button Visibility="Visible"
Height="50"
Background="Blue"
Content="Button A"/>
<Button>
<Button.Visibility>
<Windows:Visibility> Visible </Windows:Visibility>
</Button.Visibility>
<Button.Height>
<System:Double> 50 </System:Double>
</Button.Height>
<Button.Background>
<Media:SolidColorBrush>
<Media:SolidColorBrush.Color>
<Media:Color>
<Media:Color.R> 0 </Media:Color.R>
<Media:Color.G> 0 </Media:Color.G>
<Media:Color.B> 255 </Media:Color.B>
<Media:Color.A> 255 </Media:Color.A>
</Media:Color>
</Media:SolidColorBrush.Color>
</Media:SolidColorBrush>
</Button.Background>
<Button.Content> Button B </Button.Content>
</Button>
</StackPanel>
</UserControl>
In this ExampleControl I have two identical buttons, except one says Button A and the other Button B.
Notice how I referenced the properties in the first button via the properties name directly (which is mostly used) but I reference it by namespace / type for the second one. They have the same results...
Also notice that I had to include the reference to the namespace for certain types like:
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
xmlns:Windows="clr-namespace:System.Windows;assembly=PresentationCore"
XAML has a built in parser that takes the string you send and attempts to designate initialize it as the type needed for the property. See how this works for enums (Visibility : System.Windows.Visibility), primitives (Height : System.Double), and unique objects like (Background : System.Windows.Media.Brush).
Also notice that Background being a Brush type can be of any type that derives from Brush. In the example I use a SolidColorBrush which has a base of Brush.
However; I also take it a step further in the Background. Notice that not only do I assign the SolidColorBrush but I assign the Color property of the SolidColorBrush as well.
Take your time to understand how the xaml is parsing and using these features and I believe it'll answer your question about how I'm referencing SubControlA and SubControlB from my MainControl at the beginning of this answer.
The problem statement is, I end up copy and pasting following block of xaml lines in all my views.
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="WPF.Common"
lex:ResxLocalizationProvider.DefaultDictionary="global"
xmlns:lex="http://wpflocalizeextension.codeplex.com">
Is there some mechanism to put this assignment in some file and derive in all views?
You can use attached behavior, here is very simplistic (dumb) version:
public class MyBevavior
{
public static bool GetProperty(DependencyObject obj) => (bool)obj.GetValue(PropertyProperty);
public static void SetProperty(DependencyObject obj, bool value) => obj.SetValue(PropertyProperty, value);
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.RegisterAttached("Property", typeof(bool), typeof(Class), new PropertyMetadata(false, (d, e) =>
{
LocalizeDictionary.SetDesignCulture(d, "en");
ResxLocalizationProvider.SetDefaultAssembly(d, "WPF.Common");
ResxLocalizationProvider.SetDefaultDictionary(d, "global")
}));
}
Then xaml become shorter:
<Window local:MyBehavior.Property="true" ...>
...
Note, you can make it configurable with some meaningful parameter. In its current form it is bool, which is stupid, perhaps it make sense to pass en as string.
Or you can make base type for all your views, e.g. MyWindow, where you set those in constructor.
Or you can move that into a OnLoad event of every window.
why dont just use style defined in resource dictionary?
<Style x:Key="ViewStyle">
<Setter Property="lex:LocalizeDictionary.DesignCulture" Value="en" />
<Setter Property="lex:ResxLocalizationProvider.DefaultAssembly" Value="WPF.Common" />
<Setter Property="lex:ResxLocalizationProvider.DefaultDictionary" Value="global" />
</Style>
and then use the style in your views:
<UserControl Style="{StaticResource ViewStyle}">
<Page Style="{StaticResource ViewStyle}">
<Window Style="{StaticResource ViewStyle}">
BTW, Visual studio provides couple of nice features to simplify this kind of routines.
For example, you can create custom Item Template that will generate the view with all the stuff you need. The template can contain also ViewModel for the view if you wish. It really easy to create Custom Item Template.
Similarly you can create custom code snippet, which is even simpler. When you write `lex' and then press tab, it will generate the stuff for you.
Create a default style for type UserControl in your application resources.
XAML :
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="UserControl">
<Setter Property="lex:LocalizeDictionary.DesignCulture" Value="en" />
<Setter Property="lex:ResxLocalizationProvider.DefaultAssembly" Value="WPF.Common" />
<Setter Property="lex:ResxLocalizationProvider.DefaultDictionary" Value="global" />
</Style>
</Application.Resources>
</Application>
I have set up a complete application using C# .NET 4, Prism and Unity that implements the INavigationAware interface on the ViewModel of an MVVM pattern. My window (Shell.xaml) is very simple at the moment (static string for RegionName to avoid magic strings):
Shell.xaml
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.ContentRegion}" />
</Grid>
Each of my views contains buttons that allow the user to open another view using a centralized CompositeCommand to which I attach a DelegateCommand in the Shell like so:
ViewA.xaml
<Button Name="AcceptButton" Content="Accept"
Command="{x:Static Infrastructure:ApplicationCommands.NavigateCommand}"
CommandParameter="{x:Type Concrete:ViewB}"
ApplicationCommands.cs
public static class ApplicationCommands {
public static CompositeCommand NavigateCommand = new CompositeCommand();
}
ShellViewModel.cs
public ShellViewModel(IRegionManager regionManager) {
_regionManager = regionManager;
NavigateCommand = new DelegateCommand<object>(Navigate, CanNavigate);
ApplicationCommands.NavigateCommand.RegisterCommand(NavigateCommand);
}
private void Navigate(object navigatePath) {
if (navigatePath != null) {
_regionManager.RequestNavigate(RegionNames.ContentRegion, navigatePath.ToString(), NavigationCallback);
}
}
I have several more views tied in and the navigation is working great. Now comes the changes that are failing. Having random buttons on each screen is really ineffective and contrary to good design so I am trying to pull the buttons out for a centralized toolbar. I have pulled the ViewA.xaml button code out of the ViewA.xaml file ( which contains much more content but not shown for overkill reasons ) and put it into a ViewAButonn.xaml file. I then modified the Shell.xaml and add a second region:
Modified Shell.xaml
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.ContentRegion}" />
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.NavRegion}" />
</Grid>
I can add my new ViewAButton.xaml to the region without any issue and when I click it the View contents are then placed properly into the ContentRegion.
My issue arises here though. My first screen is a TOS agreement screen that cannot display the toolbar until "Accept" button is clicked. I am terrified at the thought of handling this in the ViewModel as I have it properly decoupled right now. Do I modify the View to contain a property that can be read during navigation to hide the region? If so where in the navigation process can I get access to the View that is activated by Unity? All of my views implement an IView interface that just exposes an IVewModel as per the MSDN instruction on setting up a proper prism MVVM. How can I hide this new toolbar on the TOS acceptance screen?
Based on my understanding it would not be possible to collapse a Region. The View would get Hidden but the space where the Region is located would remain empty.
If you want the TOS screen to fill the entire window, you could create two levels of Regions:
For example, you could declare a "WindowRegion" Region in the Shell View which fills the complete Window and where the TOS screen and a defined "CustomRegionsView" would get registered.
Then, the CustomRegionsView would define both Regions you mentioned above so the App can navigate between the TOS screen and any View with the two regions you described.
Therefore, when "Accept" button is clicked from the TOS screen, Navigation to the CustomRegionsView should be performed as also to the particular Views which are registered on the ContentRegion and NavRegion.
I hope you find this helpful.
It’s worth having a closer look at the “Prism for WPF Reference Implementation”, in particular, Shell.xaml in Line 173 and onwards. This’ll show you that it’s indeed possible to collapse a Prism region.
<ContentControl x:Name="ActionContent" prism:RegionManager.RegionName="{x:Static inf:RegionNames.ActionRegion}">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Grid>
<Controls:RoundedBox />
<ContentPresenter Margin="10,0,10,0" Content="{TemplateBinding Content}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
They combine this with a data converter that is being made use of within the row definitions of a grid layout:
<Grid.Resources>
<StockTraderRI:VisibilityToStarHeightConverter x:Key="VisibilityToStarHeightConverter" />
</Grid.Resources>
<Grid.Rows>
…
<RowDefinition Height="{Binding Visibility, ElementName=ActionContent, Converter={StaticResource VisibilityToStarHeightConverter}, ConverterParameter=5}" />
…
where the converter itself is defined as
public class VisibilityToStarHeightConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if ((Visibility)value == Visibility.Collapsed) {
return new GridLength(0, GridUnitType.Star);
} else {
if (parameter == null) {
throw new ArgumentNullException("parameter");
}
return new GridLength(double.Parse(parameter.ToString(), culture), GridUnitType.Star);
}
}
…
I’ve omitted a couple of things here, but the picture is clear: you don’t actually need to make use of the zindex or other workarounds.
Again, as mentioned above, the implementation I pasted here is not mine, but part of the Prism Quick Starts over at Microsoft.
So, I'm trying to design as minimalistic a UI as possible, and to that end, I need to provide hints inside textboxes, like android does. I've found many solutions to the problem (see Watermark / hint text / placeholder TextBox in WPF , How can I add a hint text to WPF textbox?) but every solution seems to use lots of XAML code, styles, triggers and the sort. What I want to do is, I want to have a textbox subclass that has a HintText property which I can use everywhere, but so far, I haven't managed to even get close. This is the closest I got:
<TextBox x:Class="MyProgram.CustomControls.HintTextBox"
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"
mc:Ignorable="d" Text="ASDF"
d:DesignHeight="174" d:DesignWidth="708">
<TextBox.Resources>
<VisualBrush x:Key="VB">
<VisualBrush.Visual>
<Label Content="{Binding Path=HintText}" Foreground="LightGray" FontSize="25"/>
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Background" Value="{StaticResource VB}"/>
</Style>
</TextBox.Style>
</TextBox>
and:
public partial class HintTextBox : TextBox
{
public HintTextBox()
{
InitializeComponent();
}
public static DependencyProperty HintTextProperty = DependencyProperty.Register("HintText", typeof(String), typeof(HintTextBox));
}
Which is missing the trigger definitions, but that's not the main problem here. My primary problem is that I can't seem to bind the HintText property. I can't assign it through XAML, and I can't bind to it for some reason. I also tried binding to the TextBox's own Text property just to see if it would work, and it didn't. What am I doing wrong? Or am I barking up the wrong tree entirely?
EDIT: I also need the same functionality for a PasswordBox, getting nowhere with that either... Why the hell did they separate TextBox and PasswordBox anyway?
The problem is, that the VisualBrush resource "VB" can be shared between all elements and label content can't be binded.
You can try use my sample TextBox with null text hint