How to inherit property values in a UserControl? - c#

I have a page where I am adding a custom control like this:
<custom:ClickableIcon x:Name="DeleteTask" Grid.Column="2" Foreground="Red" Icon="Delete" Click="DeleteTask_Click" />
In my custom control, I have the following XAML:
<UserControl x:Name="clickableIconUserControl"
x:Class="Client.UWP.Controls.ClickableIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Client.UWP.Controls" IsEnabledChanged="userControl_IsEnabledChanged">
<Grid x:Name="ohmygrid">
<SymbolIcon x:Name="CIIcon"
Foreground="{Binding Foreground, ElementName=clickableIconUserControl}"
Symbol="{Binding Icon, ElementName=clickableIconUserControl}"
Tapped="CIIcon_Tapped"/>
</Grid>
</UserControl>
This doesn't actually work. What I want is to copy the Foreground property for the control into specific places in the user control. I'd like to do this within a style sheet, direct property or whatever. In addition, I'd like to update the foreground when the foreground of the control on the page is changed.
Unfortunately, in this code (which is much simplified than the code I am actually trying to write), the Foreground is always #FF000000 in the debugger and I cannot seem to get a hold of the setting on the actual control to set it - either in code behind or as a binding.
How can I achieve this?

Related

How to change color of all shapes in window with C# and XAML?

Brief
I am trying to programmatically change the colour of specific elements at runtime. The project currently uses Telerik and I am able to change the theme at runtime: This works as expected with no issues. I can't, however, figure out how to change the fill or stroke colour at runtime of custom shape elements in XAML.
Within my project I have a ResourceDictionary file named _Icons.xaml that contains vector shapes to use as the content for other controls (such as buttons).
Code
App.xaml.cs
I am using the following code to change the theme's marker colours at runtime.
GreenPalette.Palette.MarkerColor = (Color)ColorConverter.ConvertFromString("#FF000000");
_Icons.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyNamespace">
<ControlTemplate x:Key="Box">
<Viewbox>
<Rectangle Width="357" Height="357" Fill="#000000"/>
</Viewbox>
</ControlTemplate>
<ControlTemplate x:Key="BoxOutline">
<Viewbox>
<Rectangle Width="357" Height="357" StrokeThickness="45" Stroke="#000000"/>
</Viewbox>
</ControlTemplate>
</ResourceDictionary>
MainWindow.xaml
<telerik:RadButton>
<StackPanel>
<ContentControl Template="{StaticResource Box}" Height="58"/>
<TextBlock HorizontalAlignment="Center" Margin="0,5,0,0">Box</TextBlock>
</StackPanel>
</telerik:RadButton>
<telerik:RadButton>
<StackPanel>
<ContentControl Template="{StaticResource BoxOutline}" Height="58"/>
<TextBlock HorizontalAlignment="Center" Margin="0,5,0,0">BoxOutline</TextBlock>
</StackPanel>
</telerik:RadButton>
Question
In _Icons.xaml I have the following lines:
<Rectangle Width="357" Height="357" Fill="#000000"/>
<Rectangle Width="357" Height="357" StrokeThickness="45" Stroke="#000000"/>
Given the following line in App.xaml.cs:
GreenPalette.Palette.MarkerColor = (Color)ColorConverter.ConvertFromString("#FF000000");
How can I either...
Programmatically change the values of Fill and/or Stroke (an element that only has Fill set should only change the Fill value and not add a Stroke attribute) from the App.xaml.cs file? Or ...
Bind the values in XAML for Fill or Stroke to receive the value given by my App.xaml.cs file?
Thank you for taking the time to read my question. Any help regarding this is greatly appreciated.
First i advise you to eject that controls off your resource sheet so you can actually control them properly.
When you do that, go the code behind your control and just use dependency property of type 'Color' of the 'SolidColorBrush' that is used by the background and then bind it by element name, you gotta build the project at least once before attempting to bind.
Here is how you write a dependency property
hint: in VS write 'propdp' and hit tab twice to bring up a template, but you can use mine for now.
public Color _color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("_color", typeof(Color), typeof(Fileentity), null);
after you build once go to the xalm and put this inside your rectangle:
<Grid.Background>
<SolidColorBrush Color="{Binding
_color,ElementName=YourControlName" />
</Grid.Background>
if you do it right you will be able to access this property when inserting the control on you Page like
<local:YourcontrolName _color="{x:Bind MyColorProperty }"/>
where 'MyColorProperty' is a property that implements INotifyPropertyChanged.
An alternative way is to use a datacontext directly on the usercontrol and just bind your color to one of its properties like:
public YourControl(){
this.InitializeComponent();
this.DataContext = new MyClassDataContext();
var myContext= (MyClassDataContext)this.DataContext;
_color=MyContext.MyColorProperty;}
Where MyClassDataContext is any given class that contains a Color property(MyColorProperty) of your choosing.
You need a Dependency property here as well that binds to your Controls xalm like i showed before.
I know all this is might too hard to grasp at once, thats cause it requires basic knowledge of MvvM.

Click handler of button in datatemplate not working

I have a resource dictionary combining a number of datatemplates. I'm including this resource dictionary as a ResourceDictionary.MergedDictionaries in my Page.Resources. One of my datatemplates is a ListView and while the item source and item click is working correctly, a separate button on the ListViewItem, set in the datatemplate, is not calling my click method. Im unsure about setting this up correctly.
This click method is defined in the code behind class the defines the pages Xaml including the resource dictionary and using my datatemplate for ListViewItems.
Dictionaries
DataTemplates.xaml <- ListView template here with a button click defined in the page cs, i.e. Click="MyPages_ClickMethod"
Pages
MyPage.xaml
MyPage.xaml.cs <- click method defined here, MyPages_ClickMethod()
Here is how I am setting up the button in the datatemplate:
<Button Tag="{Binding id}" Click="MultiShareSelectFileButton_Click" Background="Transparent" Visibility="{Binding multiShareSelected, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay}">
<Image Width="27" Source="ms-appx:///Assets/sharePlusIcon#2x.png" VerticalAlignment="Center" HorizontalAlignment="Center">
</Image>
</Button>
Is it possible to do this without using ICommand?
Something like: Click="{x:Bind Path=pages:ProductPage.MultiShareSelectFileButton_Click}", but this is complaining that MultiShareSelectFileButton_Click should be static
I'll get right to it. Here is the issue,
Your DataTemplate is in a resource dictionary. The resource dictionary is made for styles and converters if I may. Putting the DataTemplate in a resource dictionary is not recommended.
Why isn't it recommended?
The reason is straight, resource dictionaries are used to put global data. For ex: a control style that you might want to be available through out your app or your converters which are being used frequently.
This is because generally you would define the resource dictionary in your app.xaml which runs when your splashscreen appears.
Now if you have a lot of stuff (DataTemplates, Styles, Converters) all defined into resource dictionaries that are merged in <Application.ResourceDictionary> part of app.xaml, it's gonna have a significant impact on your app launch time, which will spoil your user's experience.
What's advised?
It's advised to keep your converters and styles not global unless you need them everywhere. For example: If you have a BoolToVisibilityConverter or a CustomRoundButtonStyle which you use only on one page/userControl out of 4. Then it doesn't make sense to load the style or converter for the other 3 Pages. So you should declare them in <Page.Resources> instead.
Same for your DataTemplate why declare it globally if you want to use it just once. Rather declare it to your <Page.Resources>. Your problem will be solved immediately as your Page will have a code-behind, so your xaml will know where to look for the method. That's where things are going wrong.
But in-case you have a single DataTemplate to be used on all your Views below is your solution:
Your Solution:
In-case you have to use it in a resource dictionary, use {x:Bind} and x:DataType="Models:YourDataContextModel" to bind your DataTemplate to your model. this ways your xaml will know exactly where to look for the method on click.
Below is a sample of it:
<DataTemplate x:Key="HelloTemplate" x:DataType="yourDefinedNameSpace:YourModel">
<Button Click="{x:Bind GoFetchData}"/>
</DataTemplate>
Where YourModel exists in a namespace defined as "yourDefinedNameSpace" in xaml and it contains a method of signature: internal void GoFetchData()
I hope this helps. Feel free to use the comments section if you have any doubts
I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!

Control not inheriting datacontext from parent?

Is there any reason for a control not to inherit the datacontext from its parent?
I've inherited a rather ugly piece of WPF code, which I've been tasked with updating. I want to change some text blocks from being hard coded strings to data bound values.
The basic layout I've got is
<Grid>
<TextBlock Text="The Caption" />
</Grid>
which I've changed to
<Grid>
<TextBlock Text="{Binding BoundCaption}" />
</Grid>
However when I run the application, the textblock is blank.
Using WPF Snoop I can see that the grid has the expected datacontext (the view's viewmodel), but the datacontext of the textblock is null. If it makes any difference, the grid is actually used in a C1TabItem header (Component One library), so the visual tree is a mass of borders, content presenters, layout rounders etc. So far as I can tell there are no styles defined in the application for textblock.
I know I can get around this by directly setting the datacontext of the textblock explictly using
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"
but this ugly hack shouldn't be necessary.

Setting background for user control in mainwindow not working

I've created a user control called "TestUserControl"
Inside my Mainwindow.xaml file I called my TestUserControl as follows:
<controls:TestUserControl DataContext="{Binding DataContext}" Background="Blue" />
The problem is the blue background isn't being reflected in the actual user control. Is there something I have to do in TestUserControl.xaml to let it accept the data when called from Mainwindow.xaml?
Thanks in advance.
A User Control as it is definition has no representation of it is properties, it is only visually represented with it is controls inside like panels and more controls. Then you can for instance do the following:
<controls:TestUserControl DataContext="{Binding DataContext}" x:Name="Instance">
<Grid Background="{Binding Background, ElementName=Instance}"/>
<controls:TestUserControl/>
And Apply all the base properties of a Control as you consider depending how you represent the control.

WPF ContextMenu = {x:Null} but still shows menu inside ContentControl

I need disable standard ContextMenu of TextBox. I've created a new WPF project and added the following:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox ContextMenu="{x:Null}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Window>
But this is what i get :
The following code works fine :
<Grid>
<TextBox ContextMenu="{x:Null}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
</Grid>
Why is this happening?
Update.
According to the accepted answer I've created a class derived from TextBox in order to be able to show parents ContextMenu.
public class TextBoxNoMenu: TextBox
{
public TextBoxNoMenu()
{
ContextMenu = null;
}
}
Why is this happening?
This is an interesting case of a control's behavior changing depending on where/how a property is set.
TextBox provides its own context menu by default. The only time it won't do this is when you explicitly set the local value of ContextMenu to null. This is what happens in your simple example where the TextBox is directly within in the Grid.
However, when you set a property inside a template, you're not actually setting a local value; you're setting a "parent template" value. If you inspect the value with DependencyPropertyHelper.GetValueSource(), you'll see the base value source is ParentTemplate instead of Local. Thus, the menu still gets overridden.
See Dependency Property Value Precedence for more information about the different kinds of dependency property value sources.
#OmegaMan's suggestion of assigning a 'hidden' context menu seems to work pretty well.
Note that while you mayhave disabled the ContextMenu on TextBox, if it's in another control, you may actually be seeing the ContextMenu of such a wrapper. Try Snooping it to see more specifically this sort of behaviour.
Note also that many of the default Control Templates throughout WPF can cause issues such as these by adding their own child objects. Seeing the default template for TextBox uses a Border and then <ScrollViewer Margin="0" x:Name="PART_ContentHost" />, you're likely seeing the ContextMenu of a child object if TextBox.
This seems to be a running issue where X:Null does not 'turn off' the default context menu. A better way would be to change it's visiblity:
<TextBox.ContextMenu>
<ContextMenu Visibility="Collapsed"/>
</TextBox.ContextMenu>
I had a similar issue, but I was generating my controls programmatically, and my parent control is a dockpanel. Based on the accepted answer, I decided to set the null value in the code behind.
<Grid>
<DockPanel>
<TextBox Name="txtBox" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
</DockPanel>
</Grid>
and then
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txtBox.ContextMenu = null;
}
EDIT: I felt this was kind of a haphazard answer, as it doesn't fully or directly solve this question. I did some digging and if you implement the method found in the answer to This Question you can find the textbox in the code-behind.
So, if you have this
<Grid>
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox Name="txtBox" VerticalAlignment="Top" HorizontalAlignment="Left" Width="50"></TextBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
Then you should be able to find your textbox by name (txtBox in this case) and set the context menu to null
TextBox myTextBox = FindChild<TextBox>(Application.Current.MainWindow, "txtBox");
myTextBox.ContextMenu = null;
Personally I'd prefer this to creating a new class with inheritance, but whatever works for you. This still doesn't answer "Why is this happening?" but I think the accepted answer does a good job of that.

Categories