WPF: how to auto show my ListView ToolTip when focus - c#

So i have ListViewItem with my object:
public class ClipboardItem : INotifyPropertyChanged
{
private string _text { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
And ToolTip and i want when my ListViewItem IsSelected=True to show my ToolTip
This is my ListViewItem CellTemplate:
<TextBlock Text="{Binding Path=Text}"
Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}"
Grid.Column="1"
Margin="5,0,0,0">
<TextBlock.ToolTip>
<ToolTip Content="{Binding Path=Text}"
Placement="Left"
PlacementRectangle="0,0,0,0"
HorizontalOffset="10"
VerticalOffset="20"
HasDropShadow="false"/>
</TextBlock.ToolTip>
</TextBlock>
When i move over my ListViewItem with my Up & Down arrows i want to auto show my TooTip and not only when MouseIsOver
i also try to dso it in my ListViewItem style:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="False" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Silver"/>
<Setter Property="BorderThickness" Value="0.5"/>
<Setter Property="ToolTip" Value="{Binding Path=Text}"/>
</MultiDataTrigger>
Non of them works and i can see my ToolTip only when MouseIsOver

In general this is a really bad idea. ToolTips have a very specific behavior, which is why WPF doesn't expose it as conveniently as WinForms did with ToolTip.Show. The correct thing to use here would be an adorner.
That said, if you are absolutely adamant about forcibly showing a ToolTip manually then it can be done with a behavior, but you're going to have to fudge some of the functionality that's normally taken care of for you:
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace YourApp.Behaviors
{
public class ToolTipBehavior : Behavior<FrameworkElement>
{
private ToolTip CurrentToolTip;
public ListViewItem ListViewItem
{
get { return (ListViewItem)GetValue(ListViewItemProperty); }
set { SetValue(ListViewItemProperty, value); }
}
// Using a DependencyProperty as the backing store for ListViewItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ListViewItemProperty =
DependencyProperty.Register("ListViewItem", typeof(ListViewItem), typeof(ToolTipBehavior),
new PropertyMetadata(null, (d, e) => (d as ToolTipBehavior)?.OnListViewItemChanged(e)));
private void OnListViewItemChanged(DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is ListViewItem)
(e.OldValue as ListViewItem).Selected -= ToolTipBehavior_Selected;
if (e.NewValue is ListViewItem)
(e.NewValue as ListViewItem).Selected += ToolTipBehavior_Selected;
}
private void ToolTipBehavior_Selected(object sender, RoutedEventArgs e)
{
if (e.Source != e.OriginalSource)
return;
if ((this.ListViewItem != null) && this.ListViewItem.IsSelected)
{
var tooltip = this.AssociatedObject.ToolTip as ToolTip;
if (tooltip != null)
{
if (this.CurrentToolTip != tooltip)
{
if (this.CurrentToolTip != null)
this.CurrentToolTip.Opened -= Tooltip_Opened;
this.CurrentToolTip = tooltip;
if (this.CurrentToolTip != null)
this.CurrentToolTip.Opened += Tooltip_Opened;
}
this.CurrentToolTip.PlacementTarget = this.AssociatedObject;
this.CurrentToolTip.IsOpen = true;
}
}
}
private async void Tooltip_Opened(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
(this.AssociatedObject.ToolTip as ToolTip).IsOpen = false;
}
}
}
Which you would then use like this:
<TextBlock Text="{Binding Path=Text}"
Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}"
Grid.Column="1"
Margin="5,0,0,0">
<i:Interaction.Behaviors>
<behaviors:ToolTipBehavior ListViewItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListViewItem}}}" />
</i:Interaction.Behaviors>
<TextBlock.ToolTip>
<ToolTip Content="{Binding Path=Text}" ToolTipService.ShowDuration="2"
...etc...

Related

Create a binding from a viewmodel collection class to a property in the datacontext

I have a viewmodel with multiple counters which are used in serval methods.
In the view model there is also a collection of class MenuItem which holds information the create the dynamic menuitems in the ribbon. On some of those menuitems i want to display the counter through a badge.
But to do this i need the bind the badge to the counter property.
In my menuitem class i have the path for the binding, but how can i thell my menuitem template to bind to the path it has in it's own binding.
Examples are simplified
public class ViewmodelSample
{
public int counter1 { get; set; }
public ICollection<MenuItem> MenuItems { get; set; } = new ObservableCollection<MenuItem>();
public void Sample()
{
MenuItems.Add(new MenuItem()
{
Name = "Test button",
CounterPath = "counter1"
});
}
public class MenuItem
{
public string Name { get; set; }
public string CounterPath { get; set; }
}
}
<ItemsControl ItemsSource="{Binding Path=MenuItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Path={Binding CounterPAth}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You cannot bind the Path property in the Binding markup extension, it is not a DependencyProperty.
You could identify the target counter with e.g. a Counter property of type int. Apply a Style to your TextBlock with triggers that provide the bindings to the corresponding counter properties on the ViewmodelSample. You need a RelativeSource binding as the counters are in the parent DataContext.
<Style x:Name="CounterTextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Counter}" Value="1">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Counter}" Value="2">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter2}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Counter}" Value="3">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter3}"/>
</DataTrigger>
</Style.Triggers>
</Style>
For now i have created a helper class.
Maybe is not perfect but this works for me (for now).
Helper class
public class DynamicBindingHelper
{
public string PropertyNaam { get; set; }
public static string GetPath(DependencyObject obj)
{
return (string)obj.GetValue(PathProperty);
}
public static void SetPath(DependencyObject obj, string value)
{
obj.SetValue(PathProperty, value);
}
public static readonly DependencyProperty PathProperty =
DependencyProperty.RegisterAttached("Path", typeof(string), typeof(DynamicBindingHelper), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnPathChanged)));
private static void OnPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || (e.NewValue is string val && val.IsNullOrWhiteSpace()))
return;
if (d == null)
return;
var binding = new Binding($"DataContext.{e.NewValue}")
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
Mode=BindingMode.OneWay
};
switch (d)
{
case TextBlock _:
BindingOperations.ClearBinding(d, TextBlock.TextProperty);
BindingOperations.SetBinding(d, TextBlock.TextProperty, binding);
break;
case Badge _:
BindingOperations.ClearBinding(d, Badge.ContentProperty);
BindingOperations.SetBinding(d, Badge.ContentProperty, binding);
break;
}
}
}
WPF usage
<dx:Badge helper:DynamicBindingHelper.Path="{Binding TellerNaam}" Padding="2,2,2,3" FontSize="10" Margin="-3,5,3,-5" />

WPF How correct modify view from ICommand with MVVM

I need to load ComboBox ItemsSource before the control is expanded. And if loading failed I want set border brush color to red and show tooltip with error. Can I do this in ICommand.Execute method or should use something like ValidationRule?
Code:
class ViewModel : INotifyPropertyChanged
{
public string Server { get {...} set {...} }
public ObservableCollection<string> ServerCollection { get; }
public ICommand LoadServerListCommand { get; }
protected ConnectionViewModel()
{
ServerCollection = new ObservableCollection<string>();
LoadServerListCommand = new DelegateCommand( LoadServerList );
}
private void LoadServerList( object param )
{
var comboBox = param as ComboBox;
if ( comboBox != null && !comboBox.IsDropDownOpen )
{
try
{
ServerCollection.Clear();
///... Load();
comboBox.BorderBrush = //default;
comboBox.ToolTip = null;
}
catch( InvalidOperationException ex )
{
comboBox.BorderBrush = //red;
comboBox.ToolTip = new ToolTip()
{
Content = ex.Message
};
}
}
}
}
XAML:
<ComboBox x:Name="cbServer" ItemsSource="{Binding ServerCollection}"
SelectedItem="{Binding Server, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown">
<i:InvokeCommandAction Command="{Binding Path=LoadServerListCommand}"
CommandParameter="{Binding ElementName=cbServer}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
You could add a property to the view model that indicates whether the loading was successful and bind to this property and change the appropriate properties of the ComboBox using a Style and a DataTrigger in the view.
Setting the BorderBrush of a ComboBox requires you to define a custom template for its ToggleButton though: https://blog.magnusmontin.net/2014/04/30/changing-the-background-colour-of-a-combobox-in-wpf-on-windows-8/
It will be easier to wrap the ComboBox in a Border element:
<Border BorderThickness="1">
<ComboBox x:Name="cbServer" ItemsSource="{Binding ServerCollection}"
SelectedItem="{Binding Server, Mode=TwoWay}">
<ComboBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.WindowFrameBrushKey}" Color="Red"/>
</ComboBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Path=LoadServerListCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="ToolTip" Value="{Binding Error}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Error.Length, FallbackValue=0}" Value="0">
<Setter Property="ToolTip" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding Error.Length, FallbackValue=0}" Value="0">
<Setter Property="BorderBrush" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
View Model:
private void LoadServerList(object parameter)
{
try
{
//throw new InvalidOperationException("test");
ServerCollection.Clear();
///... Load();
Error = string.Empty;
}
catch (InvalidOperationException ex)
{
Error = ex.Message;
}
}
private string _error;
public string Error
{
get { return _error; }
set { _error = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

CanUserAddRows New Row not saving in DataGrid

Created DataGrid and set CanUserAddRows="True"
Have a button which saves updates in the cs file:
private void Save_Click(object sender, RoutedEventArgs e)
{
UnitService unitService = new UnitService();
unitService.SaveUpdates(valuationCase);
MainWindow mainWin = new MainWindow();
mainWin.Show();
this.Close();
}
There is also a textbox not in the datagrid on the window which is editable and this is correctly saving edits with the save click button. Just the new rows aren't.
Any ideas??
datagrid definition:
<DataGrid Name="dgCommentsList" AutoGenerateColumns="False" Margin="10,196,9.953,38.204" CanUserAddRows="True" FontSize="18">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="bold" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="Type" Text="{Binding Type}" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly}" Value="False">
<Setter Property="TextBox.IsReadOnly" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsReadOnly}" Value="True">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
I think you need to set the mode of the binding for it to write back to the underlying object.Plus I noticed your DataGrid does not have an ItemsSource. I'm guessing as this was just a snippet that you left it out.
<TextBox x:Name="Type" Text="{Binding Type, Mode=TwoWay}">
You should commit the edit on the row using dataGrid.CommitEdit()
Edit: After diagnosing the issue here goes
You either need to implement INotifyPropertyChanged on your DataContext class (i.e: Viewmodel) like so:
public class ViewModel: INotifyPropertyChanged
{
private string _type;
public string Type
{
get { return _type; }
set
{
_type = value;
OnPropertyChanged("Type");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Or you extend from DependencyObject and use Dependency Properties, like so:
public class ViewModel: DependencyObject
{
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
"Type", typeof (string), typeof (ViewModel), new PropertyMetadata(default(string)));
public int Type
{
get { return (int) GetValue(TypeProperty ); }
set { SetValue(TypeProperty , value); }
}
}
Hope it helps ;)

Bind Button IsEnabled property to DataTemplate items state and one additional condition

I have an object with editable parameters collection which are bound as a ItemsSource to ItemsControl, and a property which checks if all parameter values are ok. This property bound to button's IsEnabled.
I also want to disable the button when any of textbox has validation error (Validation.HasError == true).
Thanks in advance.
XAML:
<Window x:Class="MyWPFTest.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">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=MyObject.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button IsEnabled="{Binding Path=MyObject.IsParametersOkay}">OK</Button>
</StackPanel>
</Window>
Code:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace MyWPFTest
{
public partial class MainWindow : Window
{
ObjectWithParameters _MyObject = new ObjectWithParameters();
public ObjectWithParameters MyObject { get { return _MyObject; } }
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
public class ObjectWithParameters : INotifyPropertyChanged
{
ObservableCollection<Parameter> _Parameters = new ObservableCollection<Parameter>();
public ObservableCollection<Parameter> Parameters { get { return _Parameters; } }
public event PropertyChangedEventHandler PropertyChanged;
public ObjectWithParameters()
{
var p1 = new Parameter("Parameter 1", 0); p1.PropertyChanged += ParameterChanged; Parameters.Add(p1);
var p2 = new Parameter("Parameter 2", 0); p2.PropertyChanged += ParameterChanged; Parameters.Add(p2);
}
void ParameterChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsParametersOkay"));
}
public bool IsParametersOkay
{
get { return Parameters.FirstOrDefault(p => p.Value < 0) == null; }
}
}
public class Parameter : INotifyPropertyChanged
{
double val;
public double Value
{
get { return val; }
set { val = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Value")); }
}
public string Name { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public Parameter(string name, double value) { Name = name; Value = value; }
}
}
Check out MultiTriggers.
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="#EEEEEE" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasItems" Value="false" />
<Condition Property="Width" Value="Auto" />
</MultiTrigger.Conditions>
<Setter Property="MinWidth" Value="120"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasItems" Value="false" />
<Condition Property="Height" Value="Auto" />
</MultiTrigger.Conditions>
<Setter Property="MinHeight" Value="95"/>
</MultiTrigger>
</Style.Triggers>
This is the way I solved the problem. May be it's not a very elegant solution, but it works.
I added a new property IsFormOkay to MainWindow class, which checks both controls and parameters validity. Then I bound Button.IsEnabled to this property and added TextChanged event for TextBox to notify about IsFormOkay.
Here is code added to MainWindow:
public event PropertyChangedEventHandler PropertyChanged;
public bool IsFormOkay { get { return IsValid(Items) && MyObject.IsParametersOkay; } }
public bool IsValid(DependencyObject obj)
{
if (Validation.GetHasError(obj)) return false;
for (int i = 0, n = VisualTreeHelper.GetChildrenCount(obj); i < n; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (!IsValid(child)) return false;
}
return true;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsFormOkay"));
}
And changes to XAML:
<StackPanel>
<ItemsControl x:Name="Items" ItemsSource="{Binding Path=MyObject.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBox TextChanged="TextBox_TextChanged" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button IsEnabled="{Binding Path=IsFormOkay}" Content="OK" />
</StackPanel>

Troubleshooting Binding Error 4

I'm getting the following Binding errors on my code and I don't know how to troubleshoot them. The bindings were generated by VS. I've tried adding presentation.tracesources (which is in the code below) but I get the same output as before.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='ClimateSolutions.SuperTB', AncestorLevel='1''. BindingExpression:Path=myName; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='ClimateSolutions.SuperTB', AncestorLevel='1''. BindingExpression:Path=isRequired; DataItem=null; target element is 'SuperTB' (Name='email'); target property is 'NoTarget' (type 'Object')
Here's my XAML:
<TextBox x:Class="ClimateSolutions.SuperTB"
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" Height="53" Width="296" FontSize="32"
xmlns:local="clr-namespace:ClimateSolutions"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
HorizontalAlignment="Left" Name="Blarg">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Foreground="Gray" FontSize="24" Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:SuperTB, AncestorLevel=1}, Path=myName, diagnostics:PresentationTraceSources.TraceLevel=High}">
</TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding Path=isRequired, RelativeSource={RelativeSource FindAncestor, AncestorType=local:SuperTB, AncestorLevel=1}}" Value="False">
<Setter Property="Text" Value="100" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
and here's the CS for SuperTB:
namespace ClimateSolutions
{
/// <summary>
/// Interaction logic for SuperTB.xaml
/// </summary>
public partial class SuperTB : TextBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String Property)
{
var anEvent = this.PropertyChanged;
if (anEvent != null)
{
anEvent(this, new PropertyChangedEventArgs(Property));
}
}
private String MyName = "Unicorns!";
private static DependencyProperty myNameProperty = DependencyProperty.Register("myName", typeof(String), typeof(SuperTB));
public String myName
{
get { return MyName; }
set { MyName = value; NotifyPropertyChanged("myName"); }
}
DependencyProperty isRequiredProperty = DependencyProperty.Register("isRequired", typeof(Boolean), typeof(SuperTB));
public Boolean isRequired
{
get { return (Boolean)GetValue(isRequiredProperty); }
set { SetValue(isRequiredProperty, value); }
}
public SuperTB()
{
InitializeComponent();
myName = "Unicorns!";
}
}
}
EDIT : I have updated the code according to your comment. To summarize, since this is a custom control, you are less dependant on the MVVM pattern to build your component logic (and thus use code behind in you component) as soon as your componennt itself meets this needs (to be sort, make its properties to be as much bindable as you can). For example, in the updated code, you can now bind the default property, but you can also imagine exposing properties to set the foreground colors used to diaplay control name when there is no value, and so forth.
I tried several things with you original code (included solution provided by J cooper) and nothing seemed to work. It seems that there is a lot of issues with your code.
I managed to approach a solution by making your textbox a custom control.
Here is the Generic.xaml (the visual definition of your control) :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Question_6514447">
<Style TargetType="{x:Type local:SuperTB2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SuperTB2}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox x:Name="PART_Input">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsRequired}" Value="False">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DefaultTextValue}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And here is the code behind of the control :
[TemplatePart(Name = "PART_Input")]
public class SuperTB2 : Control
{
private TextBox PART_Input;
static SuperTB2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(SuperTB2), new FrameworkPropertyMetadata(typeof(SuperTB2)));
}
public SuperTB2()
{
Loaded += SuperTb2Loaded;
}
public override void OnApplyTemplate()
{
PART_Input = GetTemplateChild("PART_Input") as TextBox;
if (PART_Input != null)
{
PART_Input.GotFocus += PartInputGotFocus;
PART_Input.LostFocus += PartInputLostFocus;
}
}
void PartInputLostFocus(object sender, RoutedEventArgs e)
{
if (PART_Input.Text == string.Empty)
{
PART_Input.Text = Name;
PART_Input.Foreground = new SolidColorBrush(Colors.Gray);
}
}
void PartInputGotFocus(object sender, RoutedEventArgs e)
{
if (PART_Input.Text.Equals(Name))
{
PART_Input.Text = string.Empty;
PART_Input.Foreground = new SolidColorBrush(Colors.Black);
}
}
void SuperTb2Loaded(object sender, RoutedEventArgs e)
{
if (PART_Input.Text == string.Empty)
{
PART_Input.Text = Name;
PART_Input.Foreground = new SolidColorBrush(Colors.Gray);
}
}
private static DependencyProperty myNameProperty =
DependencyProperty.Register("MyName", typeof(string), typeof(SuperTB2), new PropertyMetadata("Unicorns !", NameChanged));
private static void NameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public string MyName
{
get { return (string)GetValue(myNameProperty); }
set { SetValue(myNameProperty, value); }
}
DependencyProperty isRequiredProperty =
DependencyProperty.Register("IsRequired", typeof(bool), typeof(SuperTB2), new PropertyMetadata(false, IsReqChanged));
private static void IsReqChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public bool IsRequired
{
get { return (bool)GetValue(isRequiredProperty); }
set { SetValue(isRequiredProperty, value); }
}
public string DefaultTextValue
{
get { return (string)GetValue(DefaultTextValueProperty); }
set { SetValue(DefaultTextValueProperty, value); }
}
// Using a DependencyProperty as the backing store for DefaultTextValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultTextValueProperty =
DependencyProperty.Register("DefaultTextValue", typeof(string), typeof(SuperTB2), new UIPropertyMetadata("100"));
}
And an example of use of the component :
<Grid>
<StackPanel>
<Question_6514447:SuperTB2 x:Name="FirstName" IsRequired="true" DefaultTextValue="200"/>
</StackPanel>
</Grid>
With this updated code, I think you can acheive almost all the behaviors your needed !
Hope this will help !
Do not use relative source in your binding expressions. Relative source is used to access elements higher in the element tree. It seems as though you were using it in terms of object inheritance.
<Trigger Property="Text" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Foreground="Gray" FontSize="24" Text="{Binding Path=myName, diagnostics:PresentationTraceSources.TraceLevel=High}">
</TextBlock>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding Path=isRequired}" Value="False">
<Setter Property="Text" Value="100" />
</DataTrigger>

Categories

Resources