As the title says, Iam trying to show/hide a TextBox in WPF without writing code in MainWindow.xaml.cs file.
Model:
public class Person
{
public string Comment { get; set; }
}
View:
<Window x:Class="PiedPiper.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="WPF" Height="400" Width="400">
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" Name="CommentTextBox"></TextBox>
</Grid>
ViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel(Person person)
{
Comment = person.Comment;
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So the TextBox should be hidden at start, but visible when checkbox is checked. Please help!
Thanks.
You can bind TextBox.Visiblity to CheckBox.IsChecked. If you want to toggle between Hidden and Visible then you need to either write custom IValueConverter or create simple Style.Trigger
<StackPanel>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Name="CommentTextBox">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=CommentCheckBox, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
if you want to toggle between Collapsed and Visible there is an easier way and you can use build in BooleanToVisibilityConverter
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
The simplest way is to write a custom "BooleanToHiddenVisibilityConverter" and use it (like dkozl said).
It's a really simple converter and it comes in handy in many situations. I think that every descent WPF application should have one.
public sealed class BooleanToHiddenVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = false;
if (value is bool)
{
bValue = (bool)value;
}
return (bValue) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
return (Visibility)value == Visibility.Visible;
}
return false;
}
}
And use it like dkozl said:
<StackPanel>
<StackPanel.Resources>
<BooleanToHiddenVisibilityConverter x:Key="BooleanToHiddenVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked,
Converter={StaticResource BooleanToHiddenVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
Related
I want my ComboBox to be disabled when my collection is null or empty and to be enabled when I update the collection and fill it, in the same way as my "Connect" button.
I tried IsEnabled="{Binding CanConnect }", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
I typed this with MVVM:
Model.cs
internal class PuertosCollection : ObservableCollection<Puerto>
{
}
internal class Puerto
{
public string Nombre { get; set; }
public SerialPort Valor { get; set; }
public override string ToString()
{
return Nombre;
}
}
ViewModel.cs
public bool CanConnect
{
get
{
return CurrentPuerto?.Valor.IsOpen != null;
}
}
private ICommand conectarCommand;
public ICommand ConectarCommand
{
get
{
if (conectarCommand == null)
conectarCommand = new RelayCommand(new Action(Conectar), () => CanConnect);
return conectarCommand;
}
}
private void Conectar()
{
currentPuerto.Valor.Open();
}
private Puerto currentPuerto;
public Puerto CurrentPuerto
{
get { return currentPuerto; }
set
{
currentPuerto = value;
RaisePropertyChanged("CurrentPuerto");
}
}
private PuertosCollection listaPuertos;
public PuertosCollection ListaPuertos
{
get { return listaPuertos; }
set
{
listaPuertos = value;
if (value != null && value.Count > 0)
{
CurrentPuerto = value[0];
}
RaisePropertyChanged("ListaPuertos");
}
}
private ICommand listarPuertosCommand;
public ICommand ListarPuertosCommand
{
get
{
if (listarPuertosCommand == null)
listarPuertosCommand = new RelayCommand(new Action(ListarPuertos));
return listarPuertosCommand;
}
}
private void ListarPuertos()
{
ListaPuertos = Generator.Puertos();
}
View.xaml
<Window.Resources>
<vm:ConfiguracionViewModel x:Key="ConfiguracionVM"/>
<vm:DatoViewModel x:Key="DatoVM"/>
</Window.Resources>
<DockPanel>
<StackPanel DataContext="{StaticResource ConfiguracionVM}" DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Left" Margin="8">
<Button Content="Conectar" Command="{Binding ConectarCommand}" Margin="0,0,8,0"/>
<Label Content="Puerto:" VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding CanConnect }" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Command="{Binding ListarPuertosCommand}" VerticalAlignment="Center" Margin="0,0,8,0">
<StackPanel>
<Image Source="/Recursos/Imagenes/actualizar_96px.png" Width="32" Height="32" />
</StackPanel>
</Button>
<Button VerticalAlignment="Center">
<StackPanel>
<Image Source="/Recursos/Imagenes/ajustes_48px.png" Width="32" Height="32"/>
</StackPanel>
</Button>
</StackPanel>
<StatusBar DockPanel.Dock="Bottom">
<Label Content="Statusbar"/>
</StatusBar>
<DataGrid DataContext="{StaticResource DatoVM}">
</DataGrid>
</DockPanel>
I am learning MVVM with this tutorial
This is very simple.
First create a Converter that receives an integer that corresponds to the amount of items you have in the collection, if the amount is greater than zero it will return a True, if not it will return a False.
using System;
using System.Windows.Data;
namespace MyProject
{
public class CountToBoolean : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value is int) ? ((int)value) > 0 : false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Then you create an instance of the Converter in the App.xaml or in the window you want to use it.
<cv:CountToBoolean x:Key="CountToBoolean"/>
And finally you apply it to the ComboBox:
<ComboBox ItemsSource="{Binding ListaPuertos}" SelectedItem="{Binding CurrentPuerto}" IsEnabled="{Binding ListaPuertos.Count, Converter={StaticResource CountToBoolean}}" Width="Auto" VerticalContentAlignment="Center" Margin="0,0,8,0">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ListarPuertosCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
That should do it.
I tried IsEnabled="{Binding CanConnect}", but it starts disabled and is not enabled when I fill the collection with the button uppdate.
At least from your provided code, you do not trigger the property changed event for the CanConnect property when it is changed.
Disabling the combo box
You can add a style to the ComboBox that disables it if the ListaPuertos collection is null or empty.
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ListaPuertos}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding ListaPuertos.Count}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
I am trying to change the color of a shape by means of databinding and data trigger.
But i am still new to WPF and all.
Let me illustrate with an example. this is a group box
<GroupBox x:Class="Server.Host.SingleAxisControls"
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"
xmlns:host="clr-namespace:Server.Host"
mc:Ignorable="d"
d:DesignWidth="200">
<Grid>
<StackPanel Orientation="Vertical" Width="180" >
<host:MyRectangleControl x:Name="MyRectangle" />
<Button Click="OnButton_Click" Width="80" Margin="20,5,20,5">On</Button>
<Button Click="OffButton_Click" Width="80">Off</Button>
</StackPanel>
</Grid>
</GroupBox>
MyRectangleControl is a usercontrol of something like
<UserControl x:Class="Server.Host.MyRectangleControl"
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"
d:DesignHeight="30" d:DesignWidth="30">
<Grid>
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
Fill="red">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Fill"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Grid>
In the code behind the groupbox, I have something like
namespace Server.Host
{
public partial class SingleAxisControls : INotifyPropertyChanged
{
public SingleAxisControls()
{
InitializeComponent();
MyRectangle.DataContext = this;
}
private bool _test;
public bool Test
{
get { return _test; }
set
{
_test = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Test"));
}
}
}
private void OnButton_Click(object sender, RoutedEventArgs e)
{
Test = true;
}
private void OffButton_Click(object sender, RoutedEventArgs e)
{
Test = false;
}
}
I am not sure what is wrong but it doesn't seem change the color of the rectangle when i change the value of test from false to true.
This is a problem of value precedence.
When you set a DependencyProperty directly in the declaration of an Element this value has higher precedence than a value set in a style.
All you have to do is set the Fill property to Red in the Style:
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
>
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged}"
Value="True">
<Setter Property="Fill"
Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Another valid option is writing a converter to the binding. It would look like this:
class BooleanToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.LightGray);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
(You can set the colors to anything you want)
and the Xaml would look like this:
<Rectangle HorizontalAlignment="Center"
Height="25"
Margin="0,0,0,0"
Stroke="Black"
VerticalAlignment="Center"
Width="25"
Fill="{Binding Test, Converter={StaticResource b2b}}">
with this at the top of your xaml:
<UserControl.Resources>
<BooleanToBrushConverter x:Key="b2b" />
</UserControl.Resources>
Note: if your converter is in a different location you will need to include it in the namespace and preface the declaration with whatever you named the namespace i.e.
xlmns:converters="clr-namespace:Project.Converters"
in the <UserControl> tag
and then <converters: BooleanToBrushConverter x:Key="b2b" /> in the resources
I want to change an icon based on an enum.
I've created a new viewmodel for my UserControl named CallControlViewModel
public class CallControlViewModel : BaseViewModel
{
private InputTypeEnum _inputTypeEnum;
public CallControlViewModel()
{
}
public InputTypeEnum InputType
{
get { return _inputTypeEnum; }
set
{
if (_inputTypeEnum != value)
{
_inputTypeEnum = value;
NotifyPropertyChanged("InputType");
}
}
}
}
This is the baseViewModel
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify of Property Changed event
/// </summary>
/// <param name="propertyName"></param>
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is the enum
public enum InputTypeEnum
{
Empty = 0, Number = 1, Text = 2
}
Code behind usercontrol
public partial class CallControl : UserControl
{
private CallControlViewModel callControlViewModel;
public CallControl()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(CallControl_Loaded);
}
void CallControl_Loaded(object sender, RoutedEventArgs e)
{
callControlViewModel = new CallControlViewModel();
this.DataContext = callControlViewModel;
}
private void CallBox_TextChanged(object sender, TextChangedEventArgs e)
{
InputTypeEnum type = DecideInputType();
callControlViewModel.InputType = type;
}
private InputTypeEnum DecideInputType()
{
if(string.IsNullOrEmpty(CallBox.Text))
{
return InputTypeEnum.Empty;
}
if (IsNumeric(CallBox.Text))
{
return InputTypeEnum.Number;
}
return InputTypeEnum.Text;
}
And this is my Xaml:
<UserControl.Resources>
<Style x:Key="InputTypeIndicatorStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding InputType}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=NumberIndicator}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=NumberIndicator}" />
</DataTrigger>
<DataTrigger Binding="{Binding InputType}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=TextIndicator}" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="NumberIndicator">
<Border x:Name="CallIconBorder" Width="35" BorderThickness="1,0,0,0" Background="#353535"
BorderBrush="#5d5d5d" MouseLeftButtonDown="CallIconBorder_MouseLeftButtonDown" Style="{StaticResource CallBorderStyle}" >
<Image StretchDirection="DownOnly" Margin="5" Source="/Image/call.png"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="TextIndicator">
<Border x:Name="SearchIconBorder" Width="35" >
<Image StretchDirection="DownOnly" Margin="5" Source="/Image/search.png"/>
</Border>
</DataTemplate>
</UserControl.Resources>
<DockPanel x:Name="CallControlDock" VerticalAlignment="Bottom" Background="{StaticResource LightGrey}" Height="30">
<ContentControl Style="{StaticResource InputTypeIndicatorStyle}" DockPanel.Dock="Right" HorizontalAlignment="Right" />
<Border x:Name="ClearIconBorder" DockPanel.Dock="Right" Width="20" Visibility="Hidden" VerticalAlignment="Center" Margin="5,0,5,0"
MouseDown="ClearIconBorder_MouseDown" Style="{StaticResource ClearIconStyle}" Opacity="0.5">
<Image StretchDirection="DownOnly" Source="/Image/close.png" HorizontalAlignment="Left"/>
</Border>
<spinners:ucSpinnerCogs x:Name="LoadSpinner" DockPanel.Dock="Right" HorizontalAlignment="Right" Visibility="Collapsed" />
<TextBox x:Name="CallBox" TextWrapping="Wrap" FontSize="14" FontFamily="Segoe UI Semibold" HorizontalAlignment="Stretch"
Foreground="{StaticResource AlmostWhite}" VerticalAlignment="Center"
GotFocus="CallBox_GotFocus" LostFocus="CallBox_LostFocus" TextChanged="CallBox_TextChanged" KeyDown="CallBox_KeyDown"
MouseRightButtonDown="CallBox_MouseRightButtonDown"
ContextMenu="{x:Null}">
</TextBox>
</DockPanel>
When I change the InputType property I get an error in the baseViewModel:
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); ==>
InvalidCastException, Can't convert object of type
MS.Internal.NamedObject to System.Windows.Datatemplate
What am I doing wrong?
For everyone who ran into the same issue but doesn't find another answers helpful in finding what exactly is going on.
The problem occurs when you try referencing resources with StaticResource before their declaration.
In this question these resources are NumberIndicator and TextIndicator.
It is happening because StaticResource works at compile-time and cannot look forward. So to solve the issue you can move the resouces to where they are not referenced yet. Or just use run-time DynamicResource.
Right, this whole post is one messy red herring... for anyone not familiar with that saying, it means that you can forget all about the error given because when this is done properly, you won't get that error... it's misleading.
So here we go... using your Trigger method:
First, here's an enum:
public enum TestEnum
{
None, One, Two, Three
}
Now the properties:
private TestEnum enumInstance = TestEnum.None;
public TestEnum EnumInstance
{
get { return enumInstance; }
set { enumInstance = value; NotifyPropertyChanged("EnumInstance"); }
}
private ObservableCollection<TestEnum> enumCollection =
new ObservableCollection<TestEnum>() { TestEnum.None, TestEnum.One,
TestEnum.Two, TestEnum.Three };
public ObservableCollection<TestEnum> EnumCollection
{
get { return enumCollection; }
set { enumCollection = value; NotifyPropertyChanged("EnumCollection"); }
}
Now the XAML:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Width="16" Height="16" Stretch="None" Margin="0,0,0,20">
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding EnumInstance}" Value="One">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/Copy_16.png" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumInstance}" Value="Two">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/Edit_16.png" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumInstance}" Value="Three">
<Setter Property="Image.Source" Value="/WpfApplication2;component/Images/CloseRed_16.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<ComboBox ItemsSource="{Binding EnumCollection}" SelectedItem="{Binding EnumInstance}" />
</StackPanel>
I trust that you can transfer this code to your project ok. One last thing to note... if you have any more enum values than this, you'd be better off creating an EnumToBoolImageSourceConverter and Binding with that instead.
I do remember this problem from a project some years ago. We had the same problem and added code to intercept that like so:
/// <summary>
/// Tests whether the object is the 'NamedObject'. This is placed into 'DataContext' sometimes by WPF as a dummy.
/// </summary>
public static bool IsNamedObject(this object obj)
{
return obj.GetType().FullName == "MS.Internal.NamedObject";
}
We posted several questions about this on forums but never really got an answer
I'm trying to collapse a Grid by clicking on a Button. This is how my Button is represented in xaml:
<Button Grid.Column="1" Grid.RowSpan="3" Width="25" Content="<<" Click="OnClicked" x:Name="btnCollapse"></Button>
I want to collapse a Grid on clicking this Button (something like a docked window) and bring back the Grid on clicking on the Button again. This is how I do that in the code behind:
private bool clicked;
private void OnClicked(object sender, RoutedEventArgs e)
{
clicked = !clicked;
//leftPane is my grid
leftPane.Visibility = clicked ? Visibility.Collapsed:Visibility.Visible;
btnCollapse.Content = clicked ? ">>" : "<<";
}
This works fine, my question is how can I represent this logic purely in xaml rather than in the code behind?
My layout:
<Grid>
<Grid/>
<GridSplitter/>
</Grid>
<Button/>
You should bind the Visibility dependency property of your Grid to a boolean property in the DataContext (which should implement INotifyPropertyChanged) and use a BooleanToVisibilityConverter:
private bool _isGridVisible;
public bool IsGridVisible
{
get { return _isGridVisible; }
set
{
if (_isGridVisible != value)
return;
_isGridVisible = value;
OnPropertyChanged("IsGridVisible"); // This can sometimes be named RaisePropertyChanged
}
}
private void OnClick(object sender, RoutedEventArgs e)
{
IsGridVisible = !IsGridVisible;
}
In XAML:
<Grid Visibility="{Binding IsGridVisible, Converter={StaticResource BooleanToVisibilityConverter}">
<!-- stuff -->
</Grid>
How to set button content:
IValueConverter:
public class MyBooleanToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ">>" : "<<";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Button Click="OnClick" Content="{Binding IsGridVisible, Converter={StaticResource MyBooleanToStringConverter}}"/>
Style:
<Button Click="OnClick">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content" Value="<<"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsGridVisible}" Value="False">
<Setter Property="Content" Value=">>"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I have made an usercontrol that contains a TextBox with some custom behaviours and I want to bind the Text property to a property in my ViewModel.
I have isolated the problem into a sample solution and manage to update the Text property with the ViewModel property value, but when I write into the textbox and leaves the textbox my Person.Name property is not updated.
My usercontrol xaml:
<UserControl x:Class="WpfCustomUserControlBinding.TextBoxReadOnlyLooksDisabled"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Control.Resources>
<Style x:Key="readOnlyTextbox">
<Style.Triggers>
<Trigger Property="TextBoxBase.IsReadOnly" Value="True">
<Setter Property="TextBoxBase.Background" Value="WhiteSmoke" />
<Setter Property="TextBoxBase.Foreground" Value="#FF6D6D6D" />
<Setter Property="TextBox.BorderBrush" Value="DarkGray" />
<Setter Property="TextBoxBase.BorderThickness" Value="1,1,1,1" />
</Trigger>
<Trigger Property="TextBoxBase.IsReadOnly" Value="False">
<Setter Property="TextBoxBase.Background" Value="White" />
<Setter Property="TextBoxBase.Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</Control.Resources>
<TextBox Style="{StaticResource readOnlyTextbox}" x:Name="txtTextBoxBase" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
The codebehind code:
public partial class TextBoxReadOnlyLooksDisabled
{
public TextBoxReadOnlyLooksDisabled()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof (string)
, typeof (TextBoxReadOnlyLooksDisabled)
,new PropertyMetadata(OnTextChange));
private static void OnTextChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBoxReadOnlyLooksDisabled = (TextBoxReadOnlyLooksDisabled) d;
textBoxReadOnlyLooksDisabled.txtTextBoxBase.Text = (string) e.NewValue;
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Window where I try to get the sample to work:
<Window x:Class="WpfCustomUserControlBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfCustomUserControlBinding" Title="MainWindow" Height="153" Width="525">
<Window.Resources>
<src:Person x:Key="myDataSource"/>
</Window.Resources>
<Grid >
<Label Content="Plain vanilla" Height="26" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" Width="143" />
<Label Content="Messed up version" Height="26" HorizontalAlignment="Left" Margin="12,61,0,0" Name="label2" VerticalAlignment="Top" Width="143" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="152,15,0,0" x:Name="txtVanlig" VerticalAlignment="Top" Width="251" Text="{Binding Source={StaticResource myDataSource}, Path=Name, Mode=TwoWay}"/>
<src:TextBoxReadOnlyLooksDisabled Height="23" HorizontalAlignment="Left" Margin="152,61,0,0" x:Name="txtVrien" VerticalAlignment="Top" Width="251" Text="{Binding Source={StaticResource myDataSource}, Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
The sample value class:
public class Person
{
private string _name = "King Chaos";
public string Name{get{return _name;}set{_name = value;}}
}
Thanks in advance. ;)
Edit: Adding INotifyPropertyChanged does not do the trick since the set method of the Name is not accessed when updating my custom TextBox.
The problem is that the TextBox inside your TextBoxReadOnlyLooksDisabled UserControl has no two-way binding to the Text property - you only update the TextBox programmatically (in the OnTextChanged handler) when your property value changes, but not vice-versa.
Why not just drop the changed handler altogether, and add a binding instead, like this:
<UserControl x:Class="WpfCustomUserControlBinding.TextBoxReadOnlyLooksDisabled"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Control.Resources>
//...
</Control.Resources>
<TextBox Style="{StaticResource readOnlyTextbox}"
x:Name="txtTextBoxBase"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=Text, Mode=TwoWay}"/>
Don't forget to also set the DataContext accordingly:
public partial class TextBoxReadOnlyLooksDisabled : UserControl
{
public TextBoxReadOnlyLooksDisabled()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string),
typeof(TextBoxReadOnlyLooksDisabled));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Well the problem you are experiencing is caused because the Text dependency property of the TextBox inside of your custom TextBoxReadOnlyLooksDisabled is actually not bound to your "ViewModel" (the Person class) and so when you write something in that txtTextBoxBase its Text dp is changed, but the change is not propagated back to the ViewModel.
What you can do is wire the Text dp of the nested TextBox to the Text dp of your custom control with:
<TextBox x:Name="txtTextBoxBase"
Text={Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TextBoxReadOnlyLooksDisabled}}} />
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name = "King Chaos";
public string Name{
get{
return _name;
}
set{
_name = value;
if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs("Name"));
}
}
}
Simply your model must Implement INotifyPropertyChanged and raise property changed whenever your property is set, so that XAML will detect a change and refresh its value.