Create a usercontrol with overlapping content - wpf

I need to make a (user/custom) control which when 'folded open' displays content over other controls.
Like a popup, combo or menu except that the content will still open (think toggle button style).
Any leads?

Depends on many issues.
I know at least two ways to achive it. First - using <Popup />. Its easy to bind its IsOpen property to bool variable. And if you need to close it, I have got some smart behavior (which close popup when Esc key is pressed or when it lost focus).
The second way is to have a <ContentControl /> which have a style, which have a trigger, which changing ContentTemplate depending on what ever you want:
<ContentControl Grid.Row="3" Grid.ColumnSpan="2" Grid.RowSpan="2" Content="{Binding}">
<ContentControl.Style>
<Style>
<Setter Property="ContentControl.ContentTemplate" Value="{StaticResource OneTemplate}"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Property1}" Value="False"/>
<Condition Binding="{Binding Property2}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource TwoTemplate}"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I believe that there are exists many more possible ways to solve your problem!

Simplified description.
Main form
<Window>
<StackPanel Orientation="Vertical">
<ToolButton StackPanel.ZIndex="999"/>
<TextBlock>Other content</TextBlock>
</StackPanel>
</Window>
You'll need the ZIndex to indicate the overflowing control is on top
Custom control
<UserControl x:Class="ToolButton" Height="32">
<Canvas>
<ToggleButton x:Name="button">
<TextBlock>Content</TextBlock>
</ToggleButton>
<TextBlock Canvas.Top="32" Visibility="{Binding ElementName=button, Path=IsChecked, Converter={converter:BooleanToVisibility}}">Popup</TextBlock>
</Canvas>
</UserControl>
Bool to visibility Converter
public sealed class BooleanToVisibility : MarkupExtension, IValueConverter
{
public BooleanToVisibility() { }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var flag = false;
if (value is bool)
{
flag = (bool)value;
}
else if (value is bool?)
{
var nullable = (bool?)value;
flag = nullable.GetValueOrDefault();
}
if (parameter != null)
{
if (bool.Parse((string)parameter))
{
flag = !flag;
}
}
if (flag)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var back = ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
if (parameter != null)
{
if ((bool)parameter)
{
back = !back;
}
}
return back;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}

Related

Enable/Disable controls on ComboBox selection in Xaml

How to enable /disable controls like textbox,label,textblock if combobox is selected/not-selected? e.g. If selected index is greater than zero, enable controls else disable.How to bind IsEnabled properties of the control with combobox selection?
You can bind IsEnabled to the SelectedIndex property of the ComboBox and use a IValueConverter to convert it to Boolean. For instance, in your XAML (showing enabling a Button):
<ComboBox x:Name="cmbBox" ItemsSource="{Binding Source={StaticResource DataList}}"/>
<Button Grid.Column="1" IsEnabled="{Binding ElementName=cmbBox, Path=SelectedIndex, Converter={StaticResource IndexToBoolConverter}}"/>
Then you need a converter as well, such as:
public class IndexToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value > 0)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You'll also have declare the Converter as a resource, such as in your Window.
<local:IndexToBoolConverter x:Key="IndexToBoolConverter"/>
I would probably just do something like this.
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=TheCombo}"
Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox x:Name="TheCombo" Width="100">
<ComboBoxItem>Blah</ComboBoxItem>
<ComboBoxItem>Blah</ComboBoxItem>
<ComboBoxItem>Blah</ComboBoxItem>
</ComboBox>
<Button Content="Click Me" Margin="0,10"/>
</StackPanel>
</Grid>
Hope this helps, cheers!
Try with this
Dispatcher.BeginInvoke(new Action(() =>
{
ToggleButton dropDownButton = GetFirstChildOfType<ToggleButton>(cboMedicos);
if (dropDownButton != null)
{
dropDownButton.IsEnabled = false;
}
}), System.Windows.Threading.DispatcherPriority.Render);
public static T GetFirstChildOfType<T>(DependencyObject dependencyObject) where T : DependencyObject
{
if (dependencyObject == null)
{
return null;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var result = (child as T) ?? GetFirstChildOfType<T>(child);
if (result != null)
{
return result;
}
}
return null;
}

How to write WPF DataTrigger within Border Style and how to Find Element in Setter?

I need a WPF DataTrigger for the Mouse Hover functionality of a Border. The Border Contains a Button, initially its Visibility is Collapsed. The Button should be Visible only on Mouse Hover otherwise Collapsed.
<Border Width="100" Height="30" HorizontalAlignment="Center" VerticalAlignment="Top" Background="#FFF2FFC6" Margin="0,20,0,0">
<Button x:Name="btn" Content="iApp" HorizontalAlignment="Center" VerticalAlignment="Center" Width="75" Visibility="Collapsed" />
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True">
<Setter TargetName="btn" Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Note: I need only DataTrigger. Don't suggest Event Trigger.
Here I Can't able to find the TargetName, it produces the Build Error "Error 1 The name "btn" is not recognized"
TragetName cannot be used in Style.Triggers. It should be used in ControlTemplete.Triggers.
You can write code like this(Not test).
Add the namespace in you xaml
xmlns:converter="clr-namespace:yours coverter's namespace"
Add the converter in your resources
<UserControl.Resources>
<converter:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
This is your border:
<Border x:Name="m_Border" Margin="0,20,0,0">
<Button x:Name="btn" Content="iApp" Visibility="{Binding IsMouseOver,ElementName=m_Border,Converter="{StaticResource BooleanToVisibilityConverter},ConverterParameter=Normal}"}" />
</Border>
Using this converter
public enum BooleanToVisibilityConverterType
{
/// <summary>
/// Normal
/// </summary>
Normal = 1,
/// <summary>
/// Reverse
/// </summary>
Reverse = 2
}
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var targertValue = false;
if (value == null)
{
throw new Exception("BooleanToVisibilityConverter - Convert Error");
}
else if (!Boolean.TryParse(value.ToString(), out targertValue))
{
throw new Exception("BooleanToVisibilityConverter - Convert Error");
}
else
{
var parameterValue = BooleanToVisibilityConverterType.Normal;
if (parameter != null)
{
Enum.TryParse<BooleanToVisibilityConverterType>(parameter.ToString(), out parameterValue);
}
if (parameterValue == BooleanToVisibilityConverterType.Reverse)
{
return targertValue ? Visibility.Collapsed : Visibility.Visible;
}
else
{
return targertValue ? Visibility.Visible : Visibility.Collapsed;
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var targetValue = Visibility.Collapsed;
if (value == null)
{
throw new Exception("BooleanToVisibilityConverter - ConvertBack Error");
}
else if (!Enum.TryParse<Visibility>(value.ToString(), out targetValue))
{
throw new Exception("BooleanToVisibilityConverter - ConvertBack Error");
}
else
{
var parameterValue = BooleanToVisibilityConverterType.Normal;
if (parameter != null)
{
Enum.TryParse<BooleanToVisibilityConverterType>(parameter.ToString(), out parameterValue);
}
if (parameterValue == BooleanToVisibilityConverterType.Reverse)
{
return targetValue == Visibility.Visible ? false : true;
}
else
{
return targetValue == Visibility.Visible ? true : false;
}
}
}
TargetName is mostly used within control templates and not simply within styles.
From MSDN:
You can set this property to the name of any element within the scope
of where the setter collection (the collection that this setter is
part of) is applied. This is typically a named element that is within
the template that contains this setter.
Also, to achieve your need, you should set trigger to Button not for Border
<Border x:Name="border" Width="100" Height="30" HorizontalAlignment="Center" VerticalAlignment="Top" Background="#FFF2FFC6" Margin="0,20,0,0">
<Button x:Name="btn" Content="iApp" HorizontalAlignment="Center" VerticalAlignment="Center" Width="75" >
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=border, Path=IsMouseOver}" Value="false">
<Setter Property="Button.Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Border>

Setting style based on existence of an ancestor type

I have 2 sets of TextBlocks some of them are in an ItemControl and some of them are not.
I want to make a style (just based on type) which sets the background of the TextBlock if its ancestor is an ItemControl.
I can do it using the following code but my problem is that on the log (and output window) a data biding error message is displayed because of the TextBlocks which do not have ItemControl as their ancestor.
Is there a better way to do this task and avoid this error message?
<Grid>
<Grid.Resources>
<local:HasAncestorConverter x:Key="HasAncestorConverter" />
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}},
Converter={StaticResource HasAncestorConverter}}" Value="True">
<Setter Property="Background"
Value="{Binding Tag,
RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Out of ItemControl" />
<ItemsControl Tag="Blue" >
<TextBlock Text="Inside of ItemControl" />
</ItemsControl>
</StackPanel>
</Grid>
Convertor:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Error message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')
I think #Xameli solution is what you are actually looking for...
but if you simply must do it in a style then you can achieve it using VisualTreeHelper like that:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Converter={StaticResource HasAncestorConverter}}" Value="True">
<Setter Property="Background"
Value="{Binding Tag,RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
the converter:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//you probably will have to look a few levels up
var parent = VisualTreeHelper.GetParent(value) as ItemsControl;
return item != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
According to #makc's response I solved the problem this way:
<Grid>
<Grid.Resources>
<local:HasAncestorConverter x:Key="HasAncestorConverter" />
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}},
Converter={StaticResource HasAncestorConverter}}" Value="True">
<Setter Property="Background"
Value="{Binding Tag,
RelativeSource={RelativeSource
AncestorType={x:Type ItemsControl}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Out of ItemControl" />
<ItemsControl Tag="Blue" >
<TextBlock Text="Inside of ItemControl" />
</ItemsControl>
</StackPanel>
</Grid>
Converter:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter
, System.Globalization.CultureInfo culture)
{
object parent = null;
if (value != null && parameter != null &&
parameter is Type && value is DependencyObject)
{
var control = value as DependencyObject;
Type t = parameter as Type;
parent = ParentFinder.FindParent(control, t);
}
return parent != null;
}
public object ConvertBack(object value, Type targetType, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Helper class for finding the parent of specific type:
Note: This helper find any kind of parent in logical or visual tree. for example in my case ItemsControl is a parent in the logical tree, and it can be a grandparent.
class ParentFinder
{
public static object FindParent(DependencyObject child, Type parentType)
{
object parent = null;
var logicalParent = LogicalTreeHelper.GetParent(child);
var visualParent = VisualTreeHelper.GetParent(child);
if (!(logicalParent == null && visualParent == null))
{
if (logicalParent != null && logicalParent.GetType() == parentType)
parent = logicalParent;
else if (visualParent != null && visualParent.GetType() == parentType)
parent = visualParent;
else
{
if (visualParent != null)
parent = FindParent(visualParent, parentType);
if (parent == null && logicalParent != null)
parent = FindParent(logicalParent, parentType);
}
}
return parent;
}
}
Use DataTemplate for the items in ItemsControl.
<ItemsControl ....
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }"
Background="{Binding Tag,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<sys:String>Inside of ItemControl</String>
</ItemsControl>
Keep the style that you have if you need it for other setters, just remove the trigger.
You can work with FallbackValue or TargetNullValue
Check this link out:
http://dontcodetired.com/blog/post/FallbackValue-TargetNullValue-StringFormat-in-Silverlight-4.aspx

Turn off and on column visibility

I have a DataGrid with many columns and I'd like to provide the users with a drop down that allows them to select which columns they can see. I'm using the .Net 4 WPF DataGrid in a desktop application.
Does anyone know of an easy way to accomplish what I am trying to do.
I do this as follows.
I derive from the grid an add an ICommand called HideShowColumnCommand that takes as its parameter a DataGridColumn (the one I want to hide or show) and hides the column if it is visible, and shows it if it is not.
Then I use a tricky context menu that I attach to the column header that has a tick that shows the column visible/hidden state..
The context menu looks like so
<ContextMenu
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Column.DataGridOwner.Columns}">
<ContextMenu.Resources>
<local:DataGridHeaderVisibilityToBooleanConverter
x:Key="visibilityConverter" />
<BooleanToVisibilityConverter
x:Key="VisibilityOfBool" />
<DataTemplate
DataType="{x:Type DataGridColumn}">
<ContentPresenter
Content="{Binding Path=Header}"
RecognizesAccessKey="True" />
</DataTemplate>
</ContextMenu.Resources>
<ContextMenu.ItemContainerStyle>
<Style
TargetType="MenuItem">
<!--Warning dont change the order of the following two setters
otherwise the command parameter gets set after the command fires,
not much use eh?-->
<Setter
Property="CommandParameter"
Value="{Binding Path=.}" />
<Setter
Property="Command"
Value="{Binding Path=DataGridOwner.HideShowColumnCommand}" />
<Setter
Property="IsChecked"
Value="{Binding Path=Visibility, Converter={StaticResource visibilityConverter}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
The converter like this
public class DataGridHeaderVisibilityToBooleanConverter :IValueConverter{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible) {
return true;
}
else {
return false;
}
}
catch { }
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
#endregion
}

Call other element inside WPF Tree in Style

My point is this. For test i need when user check chk1 the chk2 element changed the property IsEnabled to False, but i can't do reference to chk2 element.
This is Style XAML.
<Style x:Key="styleCheckBox" TargetType="{x:Type CheckBox}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
</Style.Triggers>
</Style
Call to Style..
<StackPanel>
<CheckBox x:Name="chk1" Content="CheckBox1" Style="{StaticResource styleCheckBox}"/>
<CheckBox x:Name="chk2" Content="CheckBox2"/>
</StackPanel>
You cannot set TargetProperty in Style Trigger. This basically means that you should create a custom control derived from StackPanel which contains two checkboxes and these checkboxes are exposed as properties. Then you'll be able to define a style for that control (not the CheckBox) and set the properties you want.
Much easier way (if only needed for testing) would be this:
<StackPanel>
<StackPanel.Resources>
<local:InverseBoolConverter x:Key="InverseBoolConverter"/>
</StackPanel.Resources>
<CheckBox x:Name="chk1" Content="CheckBox1"/>
<CheckBox x:Name="chk2" Content="CheckBox2" IsEnabled="{Binding ElementName=chk1, Path=IsChecked, Converter={StaticResource InverseBoolConverter}}"/>
</StackPanel>
Where InverseBoolConverter is defined as follows:
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBoolConverter: IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if(value is bool)
return !(bool)value;
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if(value is bool)
return !(bool)value;
else
return null;
}
}

Resources