C# WPF - Change control content when another control event occurs - c#

I'm quite new to WPF and MVVM and I'm trying to create a custom WindowChrome with all the standard Window features. I'm struggling with the Maximize/Minimize window Button content: I want the content to change when the user double clicks the WindowChrome bar, in order to show the right icon:
When I double click the bar, the result should be:
I managed to change the content with the Button Triggers, but how can I change it when another control event occurs?
Thanks in advance for the help!

Give the Button a Style with triggers that set the content based on the value of Window.WindowState. This isn't an event. The button reflects the current state of the window.
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger
Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}"
Value="Maximized">
<Setter Property="Content">
<Setter.Value>
<!-- I don't know if you're using a Path or what -->
<Path Stroke="White" Data="..." />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger
Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType=Window}}"
Value="Normal">
<Setter Property="Content">
<Setter.Value>
<!-- I don't know if you're using a Path or what -->
<Path Stroke="White" Data="..." />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
It would be wiser to set the Template of the button instead, because there’s only one copy of each of those Paths, and if you reuse the style twice, they can’t be shared.
If there's some reason why this won't work with your code, show me your code.

Related

User Control Inherited From Button in WPF

I was asked to create a Radio and CheckBox variation were visually similar to a button.
With this behavior:
When the button is clicked, it change to state checked, the background is changed, when clicked again the state is changed to unchecked and the background turn into the original brush.
At first my strategy was to create a user control. But since my control will be almost equal to a button, make sense to me use inheritance.
So my question is
Is possible to create a user control that inherit from button? If so, is that a good approach? How can I do it?
One possible approach is to use ToggleButton, but completely change its appearance when IsChecked become true:
<ToggleButton>
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- IsChecked == false template -->
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- IsChecked == true template -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Use 2 different templates (e.g. <TextBlock Text="On" /> and <TextBlock Text="Off" />) to see how it works.
You can use the WPF toggle button.

c# WPF determine if DataGrid default visual validation has errors

I am a newbie in WPF, I have always done validation for various UI controls using custom ValidationRule classes, however, when using DataGrid for the first time and binding it with a simple DataTable, I found that the DataGrid has a pretty good default validation that detects the type of DataTable columns and gives a visual error if a cell value is not of the same expected type. This is pretty enough for me that I thought no need to create custom validation rules as the default one is fitting my purpose. However, I have a Submit button that I need to disable if this DataGrid has any errors, so I thought that this would be easy utilizing the Validation.HasError property using the following code:
<Button x:Name="btnSubmit" Content="Submit">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError),ElementName=dataGrid}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But unfortunately, it seems that Validation.HasError is always False whatever the value I enter in the datagrid cells in Runtime. The default visual validation is working properly, the cell gets a red border when an incorrect value is entered, however, no notification is sent that there is an error coming from the dataGrid.
Is there any way to detect within XAML that the default visual validation of the dataGrid is producing an error? or do I have to use a custom validation rule for this purpose?
You can create a global validation on your Ap.xaml file. So Your control will have a red asterisk and the error message as Tooltip . Any control of your project can use the same validation.
In App.xaml file:
<Style x:Key="AsteriskTemplate" TargetType="Control">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Red"
FontSize="14pt"
Margin="-15,0,0,0" FontWeight="Bold">*
</TextBlock>
<Border>
<AdornedElementPlaceholder Name="myControl"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGrid" BasedOn="{StaticResource AsteriskTemplate}" />

DataTrigger applied to text property of a Textbox causes its binding to stop working

I have a functional binding on my TextBox which acts as a searbar.
However, when I add the following trigger applied on the same binding (The first trigger), it stops the binding.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=SearchFilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<!-- 1st trigger -->
<DataTrigger Binding="{Binding Path=SearchFilterString}" Value="">
<Setter Property="Text" Value="Type in part name to search."/>
</DataTrigger>
<!-- 2nd trigger -->
<Trigger Property="IsFocused" Value="true">
<Setter Property="Text" Value="{x:Null}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
I also included the second trigger, to make sure after I fix the problem with the first one it won't create a endless loop situation. So please comment on that one as well.
The purpose of these two triggers is to show some guidline inside the TextBox to describe what is this texbox about and the guidline disapears as soon as TextBox gains focus and user attempts to type in the search keyword.Let me know if you believe there is a better approach on doing the same thing.
Your binding won't work, because you are overriding it with your trigger's setter: You are setting the Text property of it to a value, while you are binding the same Text property to the view model. Both things can't work at the same time.
Essentially you're trying to create something that is called "watermark" on a textbox. I like the following the solution in particular, because it doesn't modify the TextBox itself but layers an adorner ontop of it:
Watermark Behavior for TextBox
You are looking for a watermark textbox. The standard WPF TextBox does not support this behavior.
However, there are 3rd party controls available such as the one pointed out by Frank: http://wpfplayground.com/2014/06/30/watermark-behavior-for-textbox/
Ways to do your own can be found at Watermark / hint text / placeholder TextBox in WPF
Finally, you can always just overlay your own TextBox or TextBlock over the real one and hide it on focus, which will do basically the same thing.
This is how I achieved it using Brad's idea.
I simply put a Label with the watermark I wanted and overlapped my TextBox on top of it. All I am doing is setting the TextBox's Background property to Transparent when it's binding content is empty. When this happens you can see the label on the back.
Hence, the moment you start typing watermark disappears.
XAML
<!-- Watermark Label -->
<Label
Foreground="Gray"
Content="Type the part name to start the search."
/>
<!-- Search TextBox -->
<TextBox Grid.Column="0"
HorizontalAlignment="Stretch"
>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Text" Value="{Binding Path=SearchFilterString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SearchFilterString}" Value="">
<Setter Property="Background" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Outcome

Setting the DataContext of a control after a data triggered is fired

I have a view which contains a ContentControl which has its ContentTemplate changed dynamically depending on a boolean property within the view model using a data trigger.
<ContentControl>
<!-- MyFirstControl user control by default-->
<local:MyFirstControl/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The MySecondControl user control, which displays when the trigger is fired is intended to display a textblock, of which its Text property binds to a property also within the same view model.
I am obviously wrong, but my thinking was that the triggered control would inherit the same data context. Instead it is trying to use the MyFirstControl user control as its data context (I also receive this error: System.Windows.Data Error: 40 : BindingExpression path error:).
I have tried to explicitly state the data context of the triggered control with:
<local:MySecondControl DataContext="{Binding}"/>
However it is still using the default control (MyFirstControl) as its data context.
My question is, how do I force the triggered control to use the same data context as the view file it is within?
I am fairly new to the WPF scene so I hope this makes sense!
Thanks in advance.
There's a difference between Content and ContentTemplate.
Content is your actual content for the control, while ContentTemplate defines how to draw the Content
You are setting the Content property to MyFirstControl. Your trigger is changing the ContentTemplate property, so it is changing the way your Content (MyFirstControl) gets drawn so it is drawn using MySecondControl, however the Content itself is unchanged so the DataContext will still be your MyFirstControl.
You probably want to set the default ContentTemplate to MyFirstControl instead of the actual Content property.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<!-- Set default ContentTemplate -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyFirstControl />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Hope this will help:
<local:MySecondControl DataContext="{Binding Path=DataContext,
RelativeSource={RelativeSource TemplatedParent}}"/>

MouseLeftButtonDown event isn't fired in child ContentControl

I have PortItem which derived from ContentControl, TextedStackPanel derived from StackPanel which contains PortItems. And in MainWindow i have 2 StackPanels which contains TextedStackPanels . In PortItem i have overridden MouseLeftButtonDown method. But when i do this on this method isn't fired. I searched here in the forum, and found that Background property of Grid/StackPanel must be set to Transparent. I applied this, but there is no changes. What to do ?
EDIT 1
I use partial classes. I have 2 classes: PortItem.cs and PortItem.cs.xaml. I modifiy any visual changes in this XAML file.
EDIT 2
Also any mouse events aren't fired. Triggers which i use IsMouseOver are also dont work when i keep mouse on PortItem
XAML
<ContentControl x:Class="**.PortItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UI="clr-namespace:**.UIData" Width="17" Height="17" Margin="3" SnapsToDevicePixels="True" >
<Grid Background="Transparent" Name="mainGrid">
<!-- transparent extra space makes connector easier to hit -->
<Rectangle Fill="Transparent" Margin="-2"/>
<Border BorderBrush="Green" x:Name="border" BorderThickness="2">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Path=IsMouseOver}" Value="True">
<Setter Property="Border.BorderBrush" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Border.BorderBrush" Value="Blue"/>
</DataTrigger>
<!--<DataTrigger Binding="{Binding ContactPort}" Value="{x:Null}">
<Setter TargetName="border" Property="Border.BorderBrush" Value="Green"/>
</DataTrigger>-->
</Style.Triggers>
</Style>
</Border.Style>
<Image Source="/**;component/Resources/1337238611_port.png">
</Image>
</Border>
</Grid>
Make sure that you haven't set 'IsHitTestVisible' to false on your PortItem. Also, make sure no other controls are on top of it. If they are, set their 'IsHitTestVisible' property to false and then your PortItem control will get the mouse right click event. To make sure nothing is on top, declare your put your PortItem as the last thing added to your TextedStackPanel. To double check that nothing else is on top, change the background color of other controls to something really noticeable (just for testing) to see if anything is covering your PortItem control. Also, change the color on your PortItem control to verify that it is really where you think it is. Then once you get it all working, change the colors back to their original colors.
If you could give us a code sample of your XAML, that might help. If you're adding the PortItems dynamically in code behind, supply that code too.
Edit: in light of the changes you've made to your code, try to add ClipToBounds="False" to the top of your user control declaration.
<ContentControl x:Class="**.PortItem" ClipToBounds="False"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UI="clr-namespace:**.UIData" Width="17" Height="17" Margin="3" SnapsToDevicePixels="True" >
Have you created a template for your PortItem? I created the follow class to replicate your PortItem and break point on the base.OnMouseButtonDown line and it fires, I think the reason your method is not executing is because there is no visual element for the mouse to actually interact with, try adding the style below to your app and you should see the method fire properly.
public class PortItem: ContentControl
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
}
}
then in XAML I created a style to give it something to render.
<local:PortItem Margin="44,36,156,95">
<local:PortItem.Style>
<Style TargetType="{x:Type local:PortItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PortItem}">
<Border Background="Transparent">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:PortItem.Style>
</local:PortItem>
The background being Transparent that you mention you can see in the border control, if you leave the background out you are correct, the event never fires.

Categories

Resources