Binding text property of text box to variable defined on MainWindow-WPF - 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>

Related

WPF reusable label and text box row

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>

wpf Databinding using XAML not working

I am developing a small application for learning purpose. I find that when I bind ItemControl's ItemSource to a ViewModel property in XAML, it doesn't work in an expected way. i.e. It loads the underlying collection with values at the loading time, but any changes to it are not reflected.
However, if I set Itemsource in Codebehind, it works.
When the form is loaded, it shows 2 note objects. Clicking on button should show the 3rd one. I don't understand why setting DataContext using XAML doesn't update to changes in collection. I am sharing snippet of the code here. Any help greatly appreciated.
Cut-down version of XAML -
<Window x:Class="NotesApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NotesApp"
xmlns:vm="clr-namespace:NotesApp.ViewModel"
Title="MainWindow" Height="480" Width="640">
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes}" Background="Beige" >
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Value, ElementName=zoomSlider}" ScaleY="{Binding Value, ElementName=zoomSlider}" />
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="NoteBorder" Background="Green" CornerRadius="3" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding noteText}" Margin="5,3,5,3"/>
<StackPanel Grid.Row="1" Orientation="Vertical" >
<Line X1="0" Y1="0" X2="{Binding ActualWidth,ElementName=NoteBorder}" Y2="0" Stroke="Black" StrokeThickness="1"/>
<TextBlock Text="{Binding Category}" Margin="5,3,5,3"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Window>
View Code behind-
namespace NotesApp
{
public partial class MainWindow : Window
{
MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
// IT WORKS IF I BRING IN THIS STATEMENT
//NoteItemControl.ItemsSource = ViewModel.notes;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ViewModel.AddNote(new Note("note3", "Category 3"));
}
}
}
ViewModel -
namespace NotesApp.ViewModel
{
public class MainViewModel: INotifyPropertyChanged
{
ObservableCollection<Note> _notes;
public ObservableCollection<Note> notes
{
get
{ return _notes; }
set
{
_notes = value;
OnPropertyChanged("notes");
}
}
public void AddNote(Note note)
{
_notes.Add(note);
OnPropertyChanged("notes");
}
public MainViewModel ()
{
notes = new ObservableCollection<Note>();
notes.Add(new Note("note1", "Category 1"));
notes.Add(new Note("note2", "Category 2"));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs( propertyName));
}
}
}
You create a MainViewModel instance and assign it to the MainWindow's DataContext in XAML
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
The bindings in your XAML use this instance as their source object, as long as you do not explicitly specify some other source. So there is no need (and it's an error) to create another instance in code behind.
Change the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
ViewModel = (MainViewModel)DataContext;
}
Try this :
<Window.Resources>
<vm:MainViewModel x:Key="mainVM"/>
</Window.Resources>
Now use this key as a static resource wherever you bind something like :
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes,Source={StaticResource mainVM},Mode=TwoWay}" Background="Beige" >
If you do this, you dont need any datacontext

WPF MVVM Data Binding

Im trying to implement the MVVM Pattern i just want to have a TextBox that shows some initial text at startup.
this is my view: (dont care about the buttons and the listbox for now)
<Window x:Class="Friends.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Width="150" Text="{Binding Friend}"></TextBox>
<ListBox Grid.Row="1" Width="150"></ListBox>
<Button Grid.Row="2" Content="Previous" Width="150"></Button>
<Button Grid.Row="3" Content="Next" Width="150"></Button>
</Grid>
this is my model:
public class FriendsModel : INotifyPropertyChanged
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
public FriendsModel(string _initialName)
{
_firstName = _initialName;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string _newName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(_newName));
}
}
}
and this is my viewmodel:
public class FriendsViewModel
{
public FriendsModel Friend { get; set; }
public FriendsViewModel()
{
Friend = new FriendsModel("Paul");
}
}
in the code behind i have:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new FriendsViewModel();
}
}
my project is building without any errors but it doesnt show the text in my textbox. Can anyone help me?
thanks in advance
edit:
i changed it to
<TextBox Grid.Row="0" Width="150" Text="{Binding Friend.Firstname}"></TextBox>
its still not working.
The binding should point the FirstName property. WPF can not figure out by him self how to convert Friend class to string.
Text="{Binding Friend.FirstName}"
the Friend in the binding represents the full object, you must specify the membre...
try to replace{Binding Friend} by {Binding Friend.FirstName}
The DataContext is being set right after InitializeComponent() is called, this means that the bindings have already been setup, the textbox is correctly binding to the FirstName property but at the point of binding it's empty.
if you want the textbox to update when the property does you'll need to set DataContext before InitializeComponent()
public MainWindow()
{
DataContext = new FriendsViewModel();
InitializeComponent();
}
gives the result
Have you tried this:
public FriendsModel(string _initialName)
{
this.FirstName = _initialName;
}
Regards,

Accessing methods of a control that is in a Content Template

Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:
public class MyClass
{
public String Name { get; set; }
public String Description { get; set; }
}
I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:
MyControl.xaml:
<UserControl
x:Class="WpfApplication1.MyControl"
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"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="txtDescription"
Grid.Column="0"
Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />
<Button
x:Name="btnBold"
Grid.Column="1"
Content="Bold"
Click="btnBold_Click" />
</Grid>
</UserControl>
MyControl.xaml.cs:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));
public String Description
{
get { return GetValue(MyControl.DescriptionProperty) as String; }
set { SetValue(MyControl.DescriptionProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
private void btnBold_Click(object sender, RoutedEventArgs e)
{
ToggleBold();
}
public void ToggleBold()
{
if (txtDescription.FontWeight == FontWeights.Bold)
{
btnBold.Content = "Bold";
txtDescription.FontWeight = FontWeights.Normal;
}
else
{
btnBold.Content = "Unbold";
txtDescription.FontWeight = FontWeights.Bold;
}
}
}
In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:
MainWindow.xaml:
<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:this="clr-namespace:WpfApplication1">
<Grid>
<TabControl x:Name="tabItems">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
Description="{Binding Description}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyClass> myClasses = new List<MyClass>();
myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });
tabItems.ItemsSource = myClasses;
}
}
When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.
My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:
MainWindow.xaml:
<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:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="tabItems" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
x:Name="myControl"
Description="{Binding Description}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
...
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass myClass = tabItems.SelectedItem as MyClass;
MyControl myControl;
///get the instance of myControl that is contained
///in the content template of tabItems for the
///myClass item
myControl.ToggleBold();
}
...
I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.
Any help would be hugely appreciated.
You can navigate the VisualTree to find the control you're looking for.
For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:
var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
if (myControl != null)
myControl.ToggleBold();

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.

Resources