How do I correctly bind a string in my model to an TextBox in WPF? - c#

I'm getting the binding errors, e.g.
System.Windows.Data Error: 40 : BindingExpression path error: 'MyText' property not found on 'object' ''String' (HashCode=-401799582)'. BindingExpression:Path=MyText; DataItem='String' (HashCode=-401799582); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
When trying to bind a string property of my model to a TextBox and a TextBlock (the error above is the one for the TextBox).
I've tried loosely following the how-to in Microsoft's site.
in xaml:
<Window x:Class="WpfApp1.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:src="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<src:MyView x:Key="myView"/>
</Window.Resources>
<Grid>
<StackPanel>
<Label>Input:</Label>
<TextBox>
<TextBox.Text>
<Binding Source="StaticResource myView" Path="MyText" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label>In model:</Label>
<TextBlock>
<TextBlock.Text>
<Binding Source="StaticResource myView" Path="MyText"/>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Grid>
</Window>
in C#:
using System.ComponentModel;
using System.Windows;
namespace WpfApp1
{
public class MyView : INotifyPropertyChanged
{
public MyView() { }
public event PropertyChangedEventHandler PropertyChanged;
private string _myText;
public string MyText
{
set
{
if (_myText != value)
{
_myText = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyText"));
}
}
get { return _myText; }
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I was expecting the TextBlock to display my text as I type it in the TextBox, but it runs and nothing happens (which makes sense seeing as there was a binding error).
What Am I doing wrong?

writing StaticResource in a string doesn't mean that you referenced that resource. StaticResource is a markup extension and has its own syntax (curly braces):
<StackPanel>
<Label>Input:</Label>
<TextBox>
<TextBox.Text>
<Binding Source="{StaticResource myView}" Path="MyText" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label>In model:</Label>
<TextBlock>
<TextBlock.Text>
<Binding Source="{StaticResource myView}" Path="MyText"/>
</TextBlock.Text>
</TextBlock>
</StackPanel>
you can also write the same markup in significantly less verbose way:
<StackPanel>
<Label>Input:</Label>
<TextBox Text="{Binding Path=MyText, Source={StaticResource myView}, UpdateSourceTrigger=PropertyChanged}"/>
<Label>In model:</Label>
<TextBlock Text="{Binding Path=MyText, Source={StaticResource myView}}"/>
</StackPanel>
or even better - declare DataContext, not a Resource:
<Window x:Class="WpfApp1.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:src="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<src:MyView />
</Window.DataContext>
<Grid>
<StackPanel>
<Label>Input:</Label>
<TextBox Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}"/>
<Label>In model:</Label>
<TextBlock Text="{Binding MyText}"/>
</StackPanel>
</Grid>
</Window>

Related

Simple popup dialog in WPF (overlay inside Window)

I'm working on a modal dialog popup (I'm not sure about the exact UX term) that is displayed inline, inside of a control or window with darkened background.
Visual example
What I tried is putting a <ContentPresenter /> inside the XAML of the popup and then just instantiate it like this:
<local:Popup Grid.RowSpan="2">
<TextBlock Text="Popup test..." />
</local:Popup>
However, the XAML replaces the entire Popup XAML instead of being placed where the ContentPresenter is.
Q: How is the ContentPresenter here used properly?
Popup.xaml
<ContentControl
x:Class="[...].Popup"
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:[...]"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=UserControl}}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ContentControl>
Popup.xaml.cs
using System.Windows;
namespace [...]
{
public partial class Popup : ContentControlBase
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
The content of your Popup should be defined as a ControlTemplate for the ContentPresenter to work as expected here. Please refer to the following sample code.
Popup.xaml:
<ContentControl
x:Class="WpfApplication1.Popup"
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:WpfApplication1"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300"
x:Name="popup">
<ContentControl.Template>
<ControlTemplate TargetType="local:Popup">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, ElementName=popup}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</ContentControl.Template>
Popup1.xaml.cs.
public partial class Popup : ContentControl
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
Window1.xaml:
<local:Popup Title="Title...">
<TextBlock>Text...</TextBlock>
</local:Popup>

How to use more than one DataContext

Im using MVVM Light, and in my Locator I have two ViewModels. However in a page I want to use more than one ViewModels to use their properties in the page's ui elements, but how?
Here is the XAML of my Page:
<Page
x:Class="my_app.MainMenuPage"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:my_app"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Foreground="Red"
DataContext = "{Binding Source={StaticResource Locator}, Path=SettingsVM }">
Here is the code of my Locator:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<StudentsViewModel>();
SimpleIoc.Default.Register<SettingsViewModel>();
}
public StudentsViewModel StudentsVM
{
get
{
return ServiceLocator.Current.GetInstance<StudentsViewModel>();
}
}
public SettingsViewModel SettingsVM
{
get
{
return ServiceLocator.Current.GetInstance<SettingsViewModel>();
}
}
public static void Cleanup() {}
}
So i can't do something like this, obviously:
DataContext = "{Binding Source={StaticResource Locator}, Path=SettingsVM, Path=StudentsVM}">
As far as I can see you don't use 2 DataContext. You use 2 objects of one DataContext. Set DataContext to Locator (without any Path) and then specify Path=StudentsVM.PropertyA or Path=SettingsVM.PropertyC per binding
<Page ... DataContext="{Binding Source={StaticResource Locator}}">
<!-- .... -->
<TextBlock Text="{Binding StudentsVM.PropertyA}"/>
<TextBlock Text="{Binding StudentsVM.PropertyB}"/>
<TextBlock Text="{Binding SettingsVM.PropertyC}"/>
<TextBlock Text="{Binding SettingsVM.PropertyD}"/>
<!-- .... -->
</Page>
or if you have more properties to bind you can locally change DataContext for group of controls
<Page ... DataContext="{Binding Source={StaticResource Locator}}">
<!-- .... -->
<StackPanel DataContext="{Binding StudentsVM}">
<TextBlock Text="{Binding PropertyA}"/>
<TextBlock Text="{Binding PropertyB}"/>
</StackPanel>
<!-- .... -->
<StackPanel DataContext="{Binding SettingsVM}">
<TextBlock Text="{Binding PropertyC}"/>
<TextBlock Text="{Binding PropertyD}"/>
</StackPanel>
<!-- .... -->
</Page>

Binding Command on ContextMenuItem

I'm having trouble with binding a ContextMenuItem's command to my parent object. I've followed the following examples:
http://www.codeproject.com/Articles/162784/WPF-ContextMenu-Strikes-Again-DataContext-Not-Upda
RelativeSource binding from a ToolTip or ContextMenu
WPF: Binding a ContextMenu to an MVVM Command
And I've got a lot closer, but I still get the following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'SearchString' property
not found on 'object' ''MainWindow' (Name='root')'.
BindingExpression:Path=Parent.PlacementTarget.Tag.SearchString; DataItem='MenuItem'
(Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type
'ICommand')
The main window class has SearchString defined as:
public partial class MainWindow : Window
{
...
private void SearchString(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
but, obviously, the exception is never getting thrown.
I have the menu defined in a DataTemplate as follows:
<Window x:Class="CodeNaviWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="View" Height="865" Width="991"
x:Name="root"
>
<Window.Resources>
<DataTemplate x:Key="fileContentView">
<StackPanel>
<Border BorderThickness="3" BorderBrush="BurlyWood">
<avalonEdit:TextEditor
Width="400"
Height="400"
Document="{Binding Path=Document}"
IsReadOnly="True"
Tag="{Binding ElementName=root}">
<avalonEdit:TextEditor.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Search..." Command="{Binding Path=Parent.PlacementTarget.Tag.SearchString, RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</avalonEdit:TextEditor.ContextMenu>
</avalonEdit:TextEditor>
</Border>
</StackPanel>
</DataTemplate>
</Window.Resources>
...
</Window>
Can anyone see where I'm going wrong? If I change the method to be a string property then I don't get any errors, so I'm guessing that I'm somehow telling the XAML to expect a property, rather than a method.
Answering my own question here but hopefully this will prove useful for others. The solution that worked for me was to follow the answers given here: How do I add a custom routed command in WPF?
My MainWindow now looks like the following:
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
...
}
...
private void SearchString(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
public static class Commands
{
public static readonly RoutedUICommand SearchString = new RoutedUICommand("Search String", "SearchString", typeof(MainWindow));
}
}
And the XAML has the following additions:
<Window x:Class="CodeNaviWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CodeNaviWPF"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="MyApp" Height="865" Width="991"
x:Name="root"
>
<Window.CommandBindings>
<CommandBinding Command="local:Commands.SearchString" Executed="SearchString" />
</Window.CommandBindings>
<Window.Resources>
<DataTemplate x:Key="fileContentView">
<StackPanel>
<Border BorderThickness="3" BorderBrush="BurlyWood">
<avalonEdit:TextEditor
Width="400"
Height="400"
Document="{Binding Path=Document}"
IsReadOnly="True"
Tag="{Binding ElementName=root}">
<avalonEdit:TextEditor.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Search..." Command="local:Commands.SearchString" />
</ContextMenu>
</avalonEdit:TextEditor.ContextMenu>
</avalonEdit:TextEditor>
</Border>
</StackPanel>
</DataTemplate>
</Window.Resources>
...
</Window>

Combobox binding doesn't work in ItemsControl using MVVM

I have a ComboBox in ItemsControl .I use WPF and MVVM, I have problem to figure out the binding to ComboBox, would someone give me a hand for this. XAML and VM as following:
<Window x:Class="OutageManagement.Views.MarketAssignmentsView"
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"
mc:Ignorable="d"
Title="Market Selection"
WindowStartupLocation="CenterOwner"
Width="700" Height="850"
DataContext="{Binding MarketAssignmentsVM, Source={StaticResource Locator}}" >
<Grid>
<ItemsControl ItemsSource="{Binding USMarket}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding MarketName}" Height="28"
HorizontalAlignment="Left" Name="lblUSMarketName"
VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
Name="cbUSUsers" VerticalAlignment="Center" MinWidth="140"
ItemsSource="{Binding RelativeSource={RelativeSource
AncestorType=Window}, Path=UserList}"
DisplayMemberPath="UserName"
SelectedValue="{Binding SelectedUserID}"
SelectedValuePath="UserID"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel :
public class MarketAssignmentsViewModel : ViewModelBase
{
#region Data
ObservableCollection<NOCUserViewModel> _userList;
ObservableCollection<MarketAssignmentViewModel> _usMarket;
ObservableCollection<MarketAssignmentViewModel> _caMarket;
#endregion
#region Constructor
public MarketAssignmentsViewModel()
{
GetUserList();
GetMarketAssignments();
}
#endregion
#region Properties
public ObservableCollection<NOCUserViewModel> UserList
{
get { return _userList; }
}
public ObservableCollection<MarketAssignmentViewModel> USMarket
{
get { return _usMarket; }
}
public ObservableCollection<MarketAssignmentViewModel> CAMarket
{
get { return _caMarket; }
}
#endregion
.
.
.
}
The problem is that you're trying to access the UserList as a property of the Window, instead of a property of the Window's DataContext...
Modify the ItemsSource like this:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor
AncestorType=Window}, Path=DataContext.UserList}" ... />
I recommend always looking in the Output window when you have binding problems, you probably would have seen something like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'UserList' property not found on 'object' ''MarketAssignmentsView' (Name='')'.

Binding from ItemsSource context

I'm having a problem with the DataContext and the Title. The following works as intended:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>
But the Title should contain more information so I'm doing this:
<chartingToolkit:LineSeries ItemsSource="{Binding TrendDaily.Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TrendDaily.Name}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
I figured out the Title binding doesn't work because it has the "Progress" elements as his context but I wasn't able to find a working binding.
Edit:
The complete new code with binding error (Cannot find source for binding with reference 'ElementName=LineName'):
<Window x:Class="WpfApplication1.MainWindow"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<chartingToolkit:Chart Title="Trend">
<chartingToolkit:Chart.Series>
<chartingToolkit:LineSeries DataContext="{Binding TrendDaily}"
ItemsSource="{Binding Progress}" DependentValuePath="Value" IndependentValuePath="Key" x:Name="LineName">
<chartingToolkit:LineSeries.Title>
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="*"/>
</TextBlock>
</chartingToolkit:LineSeries.Title>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public TrendDailyClass TrendDaily { get; set; }
public MainWindow()
{
TrendDaily = new TrendDailyClass();
DataContext = this;
InitializeComponent();
}
}
public class TrendDailyClass
{
public Dictionary<string, double> Progress { get; set; }
public string Name { get; set; }
public TrendDailyClass()
{
Progress = new Dictionary<string, double>();
Progress.Add("10", 10);
Progress.Add("20", 20);
Name = "test";
}
}
Bind TrendDaily to the DataContext of LineSeries, then use DataContext in the inner bindings, using ElementName as:
<chartingToolkit:Chart Title="Trend"
DataContext="{Binding TrendDaily}"
x:Name="LineName">
<chartingToolkit:LineSeries ItemsSource="{Binding Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DataContext.Name, ElementName=LineName}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
Moreover, there is no need to use two TextBlock.. You can use Run (which is very lightweight class) as:
<StackPanel Orientation="Horizontal">
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="-test text"/>
</TextBlock>
</StackPanel>
It's better, as it avoids unnecessary visual element. Classes derived from UIElement are relatively heavier.
If you're first code example is working, you should be able to use the StringFormat property in your first binding:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name, StringFormat='{}{0}-test text'} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>

Categories