WPF - How to expose subelements of a UserControl - wpf

I have created a XAML UserControl that is used to enter the current date using some up/down controls. The interesting parts of the UserControl are as follows:
<UserControl x:Class="MyApp.Controls.DateEntry"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uControl="clr-namespace:MyApp.Controls"
xmlns:uConverters="clr-namespace:MyApp.Converters"
x:Name="dateEntry">
etc...
Here's where the numeric up/down controls are defined
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<uControl:NumericEntry x:Name="monthEntry" Label="Month" Style="{StaticResource SmallNumericEntry}" Maximum="12" Number="{Binding Path=Month, ElementName=dateEntry, Mode=TwoWay}" Minimum="1"/>
<uControl:NumericEntry x:Name="dayEntry" Label="Day" Style="{StaticResource SmallNumericEntry}" Margin="10,0,0,0" Maximum="31" Number="{Binding ElementName=dateEntry, Path=Day, Mode=TwoWay}" Minimum="1"/>
<uControl:NumericEntry x:Name="yearEntry" Label="Year" Style="{StaticResource LargeNumericEntry}" Margin="10,0,0,0" Maximum="9999" Number="{Binding ElementName=dateEntry, Path=Year, Mode=TwoWay}" Minimum="1"/>
</StackPanel>
You can see how certain properties of the NumericEntries are defined (e.g. For yearEntry, Maximum="9999"). Now what I want to do, is allow any anyone who uses this UserControl in their XAML code to be able to modify this property. Here's some XAML (seperate file) that uses this UserControl:
<uControl:DateEntry
x:Name="treatmentDate"
Date="{Binding Source={StaticResource currentTreatment}, Path=Date, Mode=TwoWay}"
Margin="10" />
I want to override the value of yearEntry.Maximum to be 2099. However, in the XAML file that uses the UserControl, it doesn't have visibility to yearEntry. It is possible to modify this programatically in the .cs file, but this kind of definition surely belongs in the XAML file.
Thanks in advance for your responses!

if your dateEntry class had a dependency property for maximum year, you could bind to them from any control that uses them. then your code to set the year would look like this
<uControl:NumericEntry
x:Name="yearEntry"
Label="Year"
Style="{StaticResource LargeNumericEntry}"
Margin="10,0,0,0"
Maximum="{Binding ElementName=dateEntry, Path=MaximumYear}"
Number="{Binding ElementName=dateEntry, Path=Year, Mode=TwoWay}"
Minimum="1"/>
and in your code behind you could set the max to 9999 in the dependency props definition
public int MaximumYear {
get { return (int)GetValue(MaximumYearProperty); }
set { SetValue(MaximumYearProperty, value); }
}
public static readonly DependencyProperty MaximumYearProperty =
DependencyProperty.Register("MaximumYear", typeof(int), typeof(NumericEntry), new UIPropertyMetadata(9999));
then use it like this
<uControl:DateEntry
x:Name="treatmentDate"
Date="{Binding Source={StaticResource currentTreatment}, Path=Date, Mode=TwoWay}"
MaximumYear="9999"
Margin="10" />

Anything you want to be externally visible on your UserControl generally should be a public property, event, etc, on that UserControl. Except in extremely rare situations clients should not have to drill down into the UserControl's "guts" to work with them.
In your case, you should have a MaximumYear DependencyProperty of type int declared in your UserControl. This is declared in the code-behind - use "wpfdp" template for VB or "propdp" for C# editor. (Type the template abbreviation and hit tab to get a fillable template).
Once your DependencyProperty has been created, your UserControl's XAML can bind to it:
<uControl:NumericEntry x:Name="yearEntry" Maximum="{Binding MaximumYear, ...
and your clients can use it as an ordinary property or in XAML:
dateEntry.MaximumYear = 2010;
or in the client code's XAML:
<uControl:DateEntry MaximumYear="2010" ...

Related

WPF Set Visibility on DataTemplate UI Element from MVVM View Model

I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.

Binding one ElementUI in XAML to 2 source

<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text={Binding Path=Content} Foreground={Binding Path=TextColor}/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Hi, I'm developing a book reader application in WP8. I have a stoy with a list of paragraph which I use ListBox to display. Each paragraph content is binding to a textblock as you can see in my code. In Paragraph class, I define a field call TextColor to bind the foreground color of textblock to it. Now, each time user change the color, I have to loop through all paragraph in story and change the value of TextColor. Is there any way to separately bind 2 different property (ie. the Foreground and the Text) of a ListboxItem to different source> So I'll only have to change the Foreground one time. Thank
KooKiz solution is pretty good. However, if you don't want to include any properties to manage foreground colors, or any visual property for that matter, at all you can simply make it a static resource and bind to that instead where you can modify them independently of your model.
For example, you could define a ForegroundResouces class and add in different types of foregrounds your app needs.
In App.xaml
<Application.Resources>
<local:ForegroundResouces xmlns:local="clr-namespace:YOUR-NAMESPACE" x:Key="ForegroundResouces" />
</Application.Resources>
Then define your class
public class ForegroundResouces {
public static Brush TitleForeground { get; set; }
public static Brush ContentForeground { get; set; }
// ...
}
Then define your binding
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock
Text={Binding Path=Content}
Foreground={Binding Path=ContentForeground, Source={StaticResource ForegroundResouces} />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Then you can simply change the foreground by modifying your static properties
ForegroundResources.ContentForeground = new SolidBrush(Colors.Red);
You could sort of make different set of themes but this solution is probably only worth it if you have more than one visual property to manage.
There is ways to specify a different source for your bindings. For instance, you can use ElementName to point at your listbox and retrieve its datacontext:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" Foreground="{Binding Path=DataContext.TextColor, ElementName=MyList}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
But in your case, it's probably easier to just set the Foreground property on the parent list. It will be automatically applies to all child controls:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}" Foreground="{Binding Path=TextColor}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>

WPF Element Binding in a within a Resource control doesnt work

I have some xaml that will just copy text from one text box to another:
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Height="30" Text="{Binding ElementName=src1, Path=Text}" />
<TextBox x:Name="src1" Width="100" Height="30" />
</StackPanel>
Nothing special, works fine. A bit dumb but just an example.
However if I put the StackPanel as a resource in the Window and create dynamically from code, like this:
<Window.Resources>
<StackPanel x:Key="MySP" Orientation="Horizontal">
<TextBox Width="100" Height="30" Text="{Binding ElementName=src, Path=Text}"/>
<TextBox x:Name="src" Width="100" Height="30" />
</StackPanel>
</Window.Resources>
.. then the element binding doesnt work anymore.
Why? and how to make it work? Any ideas gratefully received.
The following Xaml should work just fine
<Window ...>
<Window.Resources>
<StackPanel x:Key="MySP" Orientation="Horizontal">
<TextBox Width="100" Height="30" Text="{Binding ElementName=src, Path=Text}"/>
<TextBox x:Name="src" Width="100" Height="30" />
</StackPanel>
</Window.Resources>
<StaticResource ResourceKey="MySP"/>
</Window>
You could also use it from code
StackPanel mySP = TryFindResource("MySP") as StackPanel;
if (mySP != null)
{
this.Content = mySP;
}
However, what is the reason for you to have a StackPanel in the Windows Resoures?
If you want to be able to reuse it several times you would have to set x:Shared="False" on the Resource but then you'll get an exception saying something like Cannot register duplicate Name 'src' in this scope the second time you add it.
As far as I'm concerned you should not put that in <Window.Resources>. Only styles, static, dynamic resources and such...
http://msdn.microsoft.com/en-us/library/ms750613.aspx
<Window>
<Window.Resources>
</Window.Resources>
<StackPanel x:Key="MySP" Orientation="Horizontal">
<TextBox Width="100" Height="30" Text="{Binding ElementName=src, Path=Text}"/>
<TextBox x:Name="src" Width="100" Height="30" />
</StackPanel>
<Window>
Having a similar issue, trying to get relative binding to my source control - In my case, I'm creating a designer and need the element as a static so styles can use it's dimensions for centering calculations on a canvas.
Taking a line from [WPF Xaml Namescopes],
ResourceDictionary does not use XAML names or namescopes ; it uses keys instead, because it is a dictionary implementation.
So, directly using ElementName in a resource Dictionary simply does not work, because no name will bind without a NameScope. Also attempted reproducing your situation with Style setters, but no luck - one cannot set an object's name via a Style.
Now, the convoluted solution I cam up with is to
Create a DependencyProperty in the code-behind of the class
you're declaring this resource in.
replace ElementName=Root with RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type namespace:RootClass}} and bind
to said container directly, allowing you to bind to said
DependencyProperty whilst bypassing the names.
If you need bindings to operate between two elements in the same
StaticResource, bind the source to said DependencyProperty as
OneWayToSource or TwoWay, and the destination as OneWay or TwoWay.
1

How to modify silverlight combobox data display

I have a combobox defined as follows:
<ComboBox x:Name="cboDept" Grid.Row="0" Margin="8,8,8,8" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource cvsCategories}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto">
<sdk:Label Content="{Binding CategoryID}" Height="20" />
<sdk:Label Content="{Binding CategoryName}" Height="20" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
It works fine. However, once I select an item in the list, I want a different template to be applied to the combobox selected item being shown to the user (the item shown after the disappearance of popup). In the above case, I want only CategoryName to be displayed in the ComboBox once I select the respective item.
Can anyone let me know on how to achieve this?
thanks
What you need to do is create a ResourceDictionary containing a few defined templates yourself. In the below, ComboBoxTemplateOne and ComboBoxTeplateTwo are user controls that are set out to display the combobox in the manor you want.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="TemplateOne">
<local:ComboBoxTemplateOne />
</DataTemplate>
<DataTemplate x:Key="TemplateTwo">
<local:ComboBoxTemplateTwo />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
You will then need to create your own class that inherits from ContentControl "DataTemplateSelector", overriding OnContentChanged
Protected Overrides Sub OnContentChanged(ByVal oldContent As Object, ByVal newContent As Object)
MyBase.OnContentChanged(oldContent, newContent)
Me.ContentTemplate = SelectTemplate(newContent, Me)
End Sub
You will then need to create another class that inherits from the above DataTemplateSelector which overrides SelectTemplate ("TemplateSelectorClass"), which will return the DataTemplate defined above ("TemplateOne" or "TemplateTwo").
Also in this derived class, you will need to define a property for each of the templates you have
Public Property ComboboxTemplateOne As DataTemplate
Then head back to your XAML and n the blow XAML
<local:TemplateSelectorClass ComboboxTemplateOne="{StaticResource TemplateOne}" Content="{Binding Path=ActiveWorkspace}>
This should work, as it is effectively doing the same work as setting the "DataTemplate" property in WPF (which doesn't exist in SilverLight)
I realise there are a fair few steps here and its quite fiddly, but hopefully this will get you there. Any questions just shout.

WPF TextBlock Binding to DependencyProperty

I have what I believe to be about one of the most simple cases of attempting to bind a view to a dependencyproperty in the view model. It seems that the initial changes are reflected in the view but other changes to the DP do not update the view's TextBlock. I'm probably just missing something simple but I just can't see what it is. Please take a look...
My XAML has a status bar on the bottom of the window. I want to bind to the DP "VRAStatus".
<StatusBar x:Name="sbar" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2"
VerticalAlignment="Bottom" Background="LightBlue" Opacity="0.4" DockPanel.Dock="Bottom" >
<StatusBarItem>
<TextBlock x:Name="statusBar" Text="{Binding VRAStatus}" />
</StatusBarItem>
<StatusBarItem>
<Separator Style="{StaticResource StatusBarSeparatorStyle}"/>
</StatusBarItem>
</StatusBar>
My viewmodel has the DP defined:
public string VRAStatus
{
get { return (string)GetValue(VRAStatusProperty); }
set { SetValue(VRAStatusProperty, value); }
}
// Using a DependencyProperty as the backing store for VRAStatus.
public static readonly DependencyProperty VRAStatusProperty =
DependencyProperty.Register("VRAStatus", typeof(string), typeof(PenskeRouteAssistViewModel),new PropertyMetadata(string.Empty));
Then, in my code I set the DP:
VRAStatus = "Test Message...";
Is there something obvious here that I am missing? In my constructor for the viewmodel I set the DP like this:
VRAStatus = "Ready";
I never get the Test Message to display.
You need to add DataContext="{Binding RelativeSource={RelativeSource Self}} in .
As it turns out things were a little more complicated than I had thought (like, when is that NOT the case :) My RibbonControl was in a UserControl to get all of that XAML out of the MainWindow. It was the fact that it was in a UserControl that made it work differently with the ViewModel. I don't know why - probably one of those mysteries that won't ever be solved. But by putting my RibbonControl directly on the MainWindow, everything works as expected - both with a DP and a C# Property. Interesting. (Wish I could get back those two days of my life!)
thanks,
Bill
Try specifying the name of the DP with the Path flag in the binding like this:
<TextBlock x:Name="statusBar" Text="{Binding Path=VRAStatus}">
Bill,
When and where do you set the DataContext? I had problems in the past that when I set the DataContext before the InitializeComponent, my Bindings never executed properly.
Also, for curiosity's sake: why do you use a DP in your ViewModel instead of just a Property?
Try to specify the UpdateSourceTrigger property of the Binding:
<StatusBar x:Name="sbar" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2"
VerticalAlignment="Bottom" Background="LightBlue" Opacity="0.4" DockPanel.Dock="Bottom" >
<StatusBarItem>
<TextBlock x:Name="statusBar" Text="{Binding VRAStatus, UpdateSourceTrigger=PropertyChanged}" />
</StatusBarItem>
<StatusBarItem>
<Separator Style="{StaticResource StatusBarSeparatorStyle}"/>
</StatusBarItem>
</StatusBar>

Resources