WPF reusable label and text box row - wpf

In my application I have a form which contains a lot of rows
with the repeated pattern of :
Label and than a Textbox next to it.
<Grid.ColumnDefinitions>
</Grid.ColumnDefinitions>
I am new to wpf but is there a way to create something like a user control which contains these two controls together ?
And each time I just add this new control and modify the Label's content.

Of course there is a way and it is called UserControl. Just right click your project and select Add New Item. Then browse to add a UserControl, here is an example:
<UserControl x:Class="WpfApp.MyUserControl"
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:local="clr-namespace:WpfApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="6*" />
</Grid.ColumnDefinitions>
<Label x:Name="lbl" />
<TextBox Grid.Column="1" Height="20" Width="100" />
</Grid>
</UserControl>
Then for managing the content of the lable you will need a dependency property so that whatever is consuming your user control can bind to it (you can use regular properties too but then binding will not be possible):
public partial class MyUserControl : UserControl
{
public static readonly DependencyProperty LabelContentProperty = DependencyProperty.Register(
"LabelContent", typeof(string), typeof(MyUserControl), new PropertyMetadata(default(string),OnLabelContentChanged));
private static void OnLabelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl) d;
control.lbl.Content = e.NewValue;
}
public string LabelContent
{
get => (string) GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public MyUserControl()
{
InitializeComponent();
}
}
In case you do not want to use dependency properties then you will be fine with something similar to:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public string LabelContent
{
get => lbl.Content as string;
set => lbl.Content = value;
}
}
And then just use it!
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApp="clr-namespace:WpfApp"
mc:Ignorable="d"
WindowStartupLocation="Manual"
Title="MainWindow">
<Grid>
<wpfApp:MyUserControl LabelContent="Hi there!"/>
</Grid>
</Window>

Related

Binding text property of text box to variable defined on MainWindow-WPF

I'm newbie on WPF and I have text box and button which open folder browser dialog.
When the user select folder I would like text box will contain the selected path.
So on MainWindow I added two variables:
public partial class MainWindow : Window
{
public string outputFolderPath { get; set; }
string reducedModelFolderPath { get; set; }
}
and when user selected folder path (after open folder dialog) I updated those variables by doing (for example):
outputFolderPath = dialog.SelectedPath
In MainWindow.xaml:
<TextBox x:Name="outputFolder" Width ="200" Height="30" Grid.Row="1" Grid.Column="1" Margin="5 10">
How can I bind TextBox.Text to outputFolderPath variable?
Thanks for your assitance!
You need to set DataContext of your window to this, to access your property in XAML, and after that bind to the property. As you are binding not to DependencyProperty, you should notify your binding that property has changed, which could be done by implementing INotifyPropertyChanged interface in your Window.
I've provided sample code to show the concept.
But this is very ugly, much better to use MVVM pattern instead.
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public string outputFolderPath { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
outputFolderPath = "Some data";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(outputFolderPath)));
}
}
MainWindow.xaml
<Window x:Class="simplest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:simplest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Click="Button_Click" Content="Go" />
<TextBox x:Name="outputFolder" Width ="200" Height="30" Grid.Row="1" Grid.Column="1" Margin="5 10" Text="{Binding outputFolderPath}"/>
</Grid>
</Window>

bind the text box of user control through mainwindow text

I am working on WPF project. I have created a user control name as "CanLogPaneView". this user control contains a Text box called "txt_CanLog".
I have bind this Text box as mentioned below:
<UserControl x:Class="CANCONTROL.CanLogPaneView"
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:local="clr-namespace:CANCONTROL"
xmlns:ViewModels="clr-namespace:CANCONTROL.ViewModels;assembly=CANCONTROL.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="MainWindowViewModel" DataType="{x:Type ViewModels:MainWindowViewModel}">
</DataTemplate>
</UserControl.Resources>
<DockPanel>
**<TextBox x:Name="txt_CanLog" Text="{Binding Text, Source={StaticResource MainWindowViewModel}}" >**
</TextBox>
</DockPanel>
</UserControl>
So I have bind the text box with mainwindow property Text. My main window have a view model. there I defined the property Text as mentioned below:
public string text = string.Empty;
public string Text
{
get
{
return text;
}
set
{
text = text + value;
}
}
in Main window code: MainWindow.xaml.cs I am adding text like
this.ViewModel.Text = "\n\nAPPLICATION CONFIGURATION\r\n";
What I want is through mainwindow.xaml.cs code I want to print some data in CanLogPaneView.xaml's textBox
Your MainWindowViewModel should be binded to your Usercontrol's DataContext instead.
Also, implement INotifyPropertyChanged in your MainWindowViewModel and RaisePropertyChange at your "Text" setter
Something like the following
<Window x:Class="WpfTestProj.MainWindow"
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"
xmlns:local="clr-namespace:WpfTestProj"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=False}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Text}" />
</Grid>
public class MainViewModel : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged();
}
}
}

WPF XAML - Property update in nested controls

I have a control that exposes a string property named HeaderText in this way:
public partial class HeaderControl : UserControl
{
public static DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof(string), typeof(HeaderControl), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public HeaderControl()
{
this.DataContext = this;
InitializeComponent();
}
}
HeaderControl's Xaml:
<UserControl x:Class="Col.HMI.Controls.HeaderControl"
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="300" d:DesignWidth="300">
<Border Background="{Binding Path=HeaderBackground}" >
<TextBlock Text="{Binding Path=HeaderText}" Foreground="White" TextAlignment="Left" VerticalAlignment="Center" HorizontalAlignment="Stretch" FontFamily="Segoe UI Light" FontSize="36" Margin="5"/>
</Border>
and I want to use this HeaderControl in another UserControl, in this way:
OtherControl's Xaml:
<controls:HeaderControl Grid.Row="0" HeaderText="DEMO" />
And this works without problems. But if I bind the HeaderText property to a string property in the OtherControl ViewModel, in this way:
<controls:HeaderControl Grid.Row="0" HeaderText="{Binding Path=SummaryTitle}" />
the bind doesn't work.
This is the SummaryTitle property in the OtherControl ViewModel:
public string SummaryTitle
{
get
{
return _summaryTitle;
}
set
{
_summaryTitle = value; OnPropertyChanged("SummaryTitle");
}
}
PS: I have other controls binded to the OtherControl View Model and they work well.
You are setting DataContext of HeaderControl to itself in the constructor by doing this:
this.DataContext = this;
That means, when you apply some binding to any of the properties in HeaderControl, the Binding engine tries to find the bound property (in your case SummaryTitle) in this control, which it wont find and will fail.
So, to fix your problem, do not set the DataContext of HeaderControl to itself in the Constructor and the Binding engine will try find the properties in the correct DataContext.
Update your HeaderControl constructor to the following, and the bindings should start to work:
public HeaderControl()
{
InitializeComponent();
}
UPDATE
What you are trying to do here is, You want to have DependencyProperty named HeaderText in your UserControl, so that it's value can be set via DataBinding, and then update a value of TextBlock in your UserControl with the value of that DependencyProperty.
You can achieve this by two ways:
1) By updating TextBlock Binding to use ElementNme and Path syntax, XAML would look like this:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300"
x:Name="_This">
<Grid>
<TextBlock Text="{Binding ElementName=_This, Path=HeaderText}" FontSize="24" />
</Grid>
</UserControl>
With this approach, whenever the property HeaderText is changed either via Binding or explicitly setting the value.
2) By listening to property value changed event for HeaderText property and then updating the TextBlock accordingly.
For this approach your HeaderControl.xaml would look like:
<UserControl x:Class="WpfApplication1.HeaderControl"
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="300" d:DesignWidth="300" >
<Grid>
<TextBlock x:Name="TextBlockInUserControl"/>
</Grid>
</UserControl>
and the HeaderControl.xaml.cs
public partial class HeaderControl : UserControl
{
public static readonly DependencyProperty HeaderTextProperty;
[Category("Header Properties")]
public string HeaderText
{
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
static HeaderControl()
{
HeaderTextProperty = DependencyProperty.Register("HeaderText", typeof (string), typeof (HeaderControl),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHeaderTextPropertyChanged));
}
private static void OnHeaderTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var headerControl = (HeaderControl) dependencyObject;
headerControl.UpdateTextBlock((string) dependencyPropertyChangedEventArgs.NewValue);
}
void UpdateTextBlock(string text)
{
TextBlockInUserControl.Text = text;
}
public HeaderControl()
{
InitializeComponent();
}
}

Two way binding use a user control...binding to object, not an element?

I created an object with a simple property with a default value. I then created a user control that has a text box in it. I set the datacontext of the user control to the object.
The text box correctly shows the properties default value but I can't seem to update the property value when the user changes the text box value. I created a simple project to illustrate my code.
Thanks for the help!!
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private string _titleValue;
public string TitleValue
{
get
{
return _titleValue;
}
set
{
_titleValue = value;
textBox1.Text = _titleValue;
}
}
public static readonly DependencyProperty TitleValueProperty = DependencyProperty.Register(
"TitleValue", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(new PropertyChangedCallback(titleUpdated))
);
//Don't think I should need to do this!!!
private static void titleUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((UserControl1)d).TitleValue = (string)e.NewValue;
}
}
<UserControl x:Class="WpfApplication1.UserControl1"
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="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="94,97,0,0" Name="textBox1" VerticalAlignment="Top" Width="120"
Text="{Binding Path=TitleValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dummy = new DummyObject("This is my title.");
userControl11.DataContext = dummy;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("The value is: " + ((DummyObject)userControl11.DataContext).Title);
}
}
<Window x:Class="WpfApplication1.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" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:UserControl1 HorizontalAlignment="Left" Margin="95,44,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="191" Width="293"
TitleValue="{Binding Path=Title, Mode=TwoWay}"/>
<Button Content="Check Value" Height="23" HorizontalAlignment="Left" Margin="20,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
The DataContext on your usercontrol isn't set. Specify a Name for it (I usually call mine "ThisControl") and modify the TextBox's binding to Text="{Binding ElementName=ThisControl, Path=TitleValue, Mode=TwoWay}". You can also set the DataContext explicitly, but I believe this is the preferred way.
It seems like the default DataContext should be "this", but by default, it's nothing.
[edit] You may also want to add , UpdateSourceTrigger=PropertyChanged to your binding, as by default TextBoxes' Text binding only updates when focus is lost.

How can I access DependencyProperty values from my code-behind constructor?

I have a UserControl called SmartForm which has a DependencyProperty called Status.
In my Window1.xaml, I have the element <local:SmartForm Status="Ready"/>.
I would think then in the constructor of the SmartForm object, that Status would equal "Ready" but instead it equals null.
Why is then the value of the Status property NULL in the constructor of SmartForm?
If not in the UserControl constructor, when do I have access to the value, then?
Window1.xaml:
<Window x:Class="TestPropertyDefine23282.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestPropertyDefine23282"
Title="Window1" Height="300" Width="300">
<Grid>
<local:SmartForm Status="Ready"/>
</Grid>
</Window>
SmartForm.xaml:
<UserControl x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock x:Name="TestingMessage"/>
</Grid>
</UserControl>
SmartForm.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace TestPropertyDefine23282
{
public partial class SmartForm : UserControl
{
public SmartForm()
{
InitializeComponent();
TestingMessage.Text = Status; //WHY IS STATUS NOT YET SET HERE?
}
#region DependencyProperty: Status
public string Status
{
get
{
return (string)GetValue(StatusProperty);
}
set
{
SetValue(StatusProperty, value);
}
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(SmartForm),
new FrameworkPropertyMetadata());
#endregion
}
}
<local:SmartForm Status="Ready"/>
Translates to:
SmartForm f = new SmartForm();
f.Status = Status.Ready;
You will have access to that value when the setter is called.
You can set that testing message as:
...
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(SmartForm),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnStatusChanged)));
public static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((SmartForm)d).TestingMessage.Text = e.NewValue.ToString();
}
...
Or as:
<UserControl
x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestPropertyDefine23282"
Height="300" Width="300"
>
<Grid>
<TextBlock
x:Name="TestingMessage"
Text="{Binding Path=Status, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SmartForm}}}"
/>
</Grid>
</UserControl>
Szymon Rozga exlained the problem in a great way. You check the parameter before it is set but after the constructor is initialized.
A good solution is using the loaded event instead like so:
(Untested)
public SmartForm()
{
InitializeComponent();
Loaded += (sender, args) =>
{
TestingMessage.Text = Status;
};
}
This is kind of tertiary, but why do you need this setter at all?
<UserControl x:Class="TestPropertyDefine23282.SmartForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Control"
Height="300" Width="300">
<Grid>
<TextBlock Text="{Binding Path=Status, ElementName=Control}" />
</Grid>
</UserControl>

Resources