I am a newbie in WPF, I have a problem concern binding two different ViewModels to two UserControls
that will be attached to two Tabpages in a Tabcontrol.
My code snippets are as follows:
MainWindow.xaml
<Window.Resources>
<local:UserControl1Model x:Key="Control1Model" />
<local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>
<Grid HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592">
<Grid HorizontalAlignment="Left" Height="45" Margin="0,330,-1,-45" VerticalAlignment="Top" Width="593">
<Button Content="Button" HorizontalAlignment="Left" Margin="490,5,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
<TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592" >
<TabItem x:Name="UserControl1TabItem" Header="User Control 1" >
<Grid x:Name="UserControl1Tabpage" Background="#FFE5E5E5" Margin="0,0,-4,-2" Height="300" VerticalAlignment="Top" IsEnabled="true" >
<local:UserControl1 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control1Model}}" />
</Grid>
</TabItem>
<TabItem x:Name="UserControl2TabItem" Header="User Control 2">
<Grid x:Name="UserControl2Tabpage" Background="#FFE5E5E5">
<local:UserControl2 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control2Model}}" />
</Grid>
</TabItem>
</TabControl>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private UserControl1Model _userControl1Model = new UserControl1Model();
private UserControl2Model _userControl2Model = new UserControl2Model();
public MainWindow()
{
InitializeComponent();
_userControl1Model.Message = "Hello";
_userControl2Model.Message = "Test";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Will do something
}
}
UserControl1Model.cs
public class UserControl1Model : INotifyPropertyChanged
{
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public UserControl1Model()
{
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string message)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(message));
}
}
#region INotifyPropertyChanged Members
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
For trying purpose, the content of UserControl2Model.cs is as same as UserControl1Model.cs
UserControl1.xaml
<UserControl.Resources>
<app:UserControl1Model x:Key="Control1Model" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control1Model}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol1ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
UserControl2.xaml
<UserControl.Resources>
<app:UserControl2Model x:Key="Control2Model" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control2Model}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
For trying purpose, the content of UserControl2.xaml.cs is as same as UserControl1.xaml.cs
My problem is the initial values, "Hello" and "Test" for the two user controls, which are initialized in MainWindow.xaml.cs cannot
be "binded" into the user controls textboxes. What am I doing wrong or missing?
When you declare resources like this
<Window.Resources>
<local:UserControl1Model x:Key="Control1Model" />
<local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>
You are actually constructing new instances of UserControl1Model and UserControl2Model instead using the ones you declared in MainWindow.cs
Also you are not creating any ViewModel for the MainWindow. You should create a MainWindowViewModel like such
public class MainWindowViewModel : INotifyPropertyChanged
{
public ViewModelLocator
{
this.FirstModel= new UserControl1Model
{
Message = "Hello";
}
this.SecondModel = new UserControl2Model
{
Message = "Test";
}
}
private UserControl1Model firstModel
public UserControl1Model FirstModel
{
get
{
return this.firstModel;
}
set
{
this.firstModel= value;
OnPropertyChanged("FirstModel");
}
}
// Same for the UserControl2Model
// implementation of the INotifyPropertyChanged
}
Also you would need to set the DataContext for the MainWindow.
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
And remove the resources from the UserControl xamls. You are already defining the DataContext in the MainWindow.xaml but the binding should be bound from the MainWindowViewModel as such.
<local:UserControl1 VerticalAlignment="Top" DataContext="{Binding FirstModel}" />
Create a ViewModelLocator. you can find various sites on the internet for this subject.
a simple one would be:
public class ViewModelLocator
{
public UserControl1Model UserControl1Model { get; set; } = new UserControl1Model();
public UserControl2Model UserControl2Model { get; set; } = new UserControl2Model();
public ViewModelLocator
{
UserControl1Model.Message = "Hello";
UserControl2Model.Message = "Test";
}
}
then you can use it in your views
<UserControl.Resources>
<app:ViewModelLocator x:Key="ViewModelLocator" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding UserControl2Model Source={StaticResource ViewModelLocator}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
Suppose, I create this instance in the xaml as follows: <Window.Resources> <local:MainWindowViewModel x:Key="MainWindowViewModel" /> </Window.Resources> instead of creating inside MainWindow.xaml.cs. Then, how can I reference this instance inside MainWindow.xaml.cs, e.g. to get value from MainWindowViewModel.ViewModelLocator.FirstModel.Message ?
Like this:
MainWindowViewModel viewModel = this.Resources["MainWindowViewModel"] as MainWindowViewModel;
//access any properties of viewModel here...
Related
I need some help. I created a custom User Control, and inserted it into the Main Window. However, Im not able to bind a Property in the Window to a DependencyProperty in the User Control.
Here's the User Control code.
XAML:
<UserControl x:Class="SomeExample.UCFullName"
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:SomeExample"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="First Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="FirstName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="Last Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="LastName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
</StackPanel>
And here's the code behind
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SomeExample
{
public partial class UCFullName : UserControl, INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implementation
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName");
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueFullNameProperty =
DependencyProperty.Register("ValueFullName", typeof(string), typeof(UCFullName), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public UCFullName()
{
InitializeComponent();
}
private void TxtBlock_TextChanged(object sender, TextChangedEventArgs e)
{
ValueFullName = FirstName.Text + " " + LastName.Text;
}
}
}
This is how it looks:
And here's the code of the Main Window:
<Window x:Class="SomeExample.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:SomeExample"
mc:Ignorable="d"
Title="Some Example" Height="200" Width="400">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" Name="SpManual" Background="Black">
<GroupBox Header="Name" Foreground="White" FontSize="14" Name="groupBoxCoordinateStart" >
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, Mode = TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"></local:UCFullName>
</GroupBox>
<StackPanel Name="SpBtnInsert" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Visible">
<Button Name="btnShowFullName" BorderBrush="White" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="2,2,0,0" Background="Transparent" Content="Show Name" Foreground="White" Width="98" Click="BtnShowFullName_Click"></Button>
</StackPanel>
</StackPanel>
And the code behind:
using System.Windows;
namespace SomeExample
{
/// <summary>
/// Lógica de interacción para MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string PropertyFullName { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void BtnShowFullName_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Current full name :" + PropertyFullName);
}
}
}
And of course, I expected that when I pressed the button, I got a message with the full name entered by the user. However, I got nothing.
Edit: Here's the solution to the problem, for people who visit this page with a similar problem.
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, RelativeSource={RelativeSource AncestorType=Window}}"></local:UCFullName>
You are binding to the wrong AncestorType. Instead of UserControl the type must be Window. Window extends Control but not UserControl.
<local:UCFullName ValueFullName="{Binding Path=PropertyFullName, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}}" />
Also because you set the Binding.Mode to TwoWay, the binding source PropertyFullName must be able to notify the binding target ValueFullName about value changes. To achieve this, you need to implement PropertyFullName as a DependencyProperty to enable two way binding.
As a a side note:
The following code can be problematic
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName"); // This line might never get called
}
}
This is just a CLR wrapper for the actual DependencyProperty and will never be invoked by the framework. When using the binding on this property the wrapper will never get called and therefore the event will never get raised.
As BionicCode has pointed out, you can change the AncestorType. Another option is to set DataContext of the Window.
You can either do it in the constructor
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
or in the XAML.
<Window DataContext="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}">
This way you don't have to specify source in your bindings (as long as you bind to code-behind properties).
I use .NETFramework,Version=v4.6.1
I have a Window, MainWindow. This is the XAML:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" />
</StackPanel>
</Grid>
</Window>
Note the:
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
The DataContext is set as follows in the MainWindow.xaml.cs constructor:
this.DataContext = new MainViewModel();
In the <Frame>, a Page Dashboard.xamlis loaded.
The page Dashboard.xaml has the source:
<Page x:Class="VexLibrary.DesktopClient.Views.Pages.Dashboard"
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:VexLibrary.DesktopClient.Views.Pages"
mc:Ignorable="d"
d:DesignHeight="460" d:DesignWidth="690"
Title="Page1">
<Grid Width="690" Height="460" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Members, Users, Books -->
<!-- Returns, Subscriptions, Statistics -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
</Grid>
</Page>
In the Dashboard.xaml.cs constructor, I have defined the DataContext like this: DataContext = new DashboardViewModel();
The DashboardViewModel.cs source code is like this (omitted namespaces)
namespace VexLibrary.DesktopClient.ViewModels
{
class DashboardViewModel : ViewModel
{
private MainViewModel parentViewModel;
public DashboardViewModel()
{
this.parentViewModel = new MainViewModel();
}
public ICommand ViewStatistics
{
get
{
return new ActionCommand(p => this.parentViewModel.LoadPage("Statistics"));
}
}
}
}
Now, in this code, notice the Button with the Command:
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
It successfully calls the Command and the parent LoadPage method is executed correctly. The parent viewmodel looks like this:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private string currentPageTitle;
public string CurrentPageTitle
{
get
{
return this.currentPageTitle;
}
set
{
currentPageTitle = value;
NotifyPropertyChanged();
}
}
public void LoadPage(string pageName)
{
this.CurrentPageTitle = pageName;
Console.WriteLine(CurrentPageTitle);
}
}
}
The CurrentPageTitle is successfully updated. However, it is not updated in the view.
The parent view model inherits ViewModel which basically has this code:
namespace VexLibrary.Windows
{
public abstract class ViewModel : ObservableObject, IDataErrorInfo
{
public string this[string columnName]
{
get
{
return OnValidate(columnName);
}
}
[Obsolete]
public string Error
{
get
{
throw new NotImplementedException();
}
}
protected virtual string OnValidate(string propertyName)
{
var context = new ValidationContext(this)
{
MemberName = propertyName
};
var results = new Collection<ValidationResult>();
bool isValid = Validator.TryValidateObject(this, context, results, true);
if (!isValid)
{
ValidationResult result = results.SingleOrDefault(p =>
p.MemberNames.Any(memberName =>
memberName == propertyName));
return result == null ? null : result.ErrorMessage;
}
return null;
}
}
}
ObservableObject.cs:
namespace VexLibrary.Windows
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// [CallerMemberName] automatically resolves the property name for us.
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
Console.WriteLine(handler == null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
After debugging, I found out, the NotifyPropertyChanged is invoked, but the handler is always null. How do I fix this? This is not updating the text in the MainWindow.xaml. I tested to see if the property value is changed, and yes, it is changed in the MainViewModel.cs
Also, I tested whether the label itself is visible or not. For that, I gave the variable a value and it correctly displays, but it is not updated.
The DashboardViewModel is instantiating a new instance of the MainViewModel rather than using the instance assigned to the DataContext of the MainWindow (and therefore the instance the view is bound to).
For your code to work you need to pass the correct instance of the MainViewModel to the DashboardViewModel as it is this instance that will have a handler for the property changed event.
EDIT: As per the comment below, you should instantiate your sub viewmodels as follows:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private ViewModel _currentViewModel;
public MainViewModel()
{
_currentViewModel = new DashboardViewModel(this);
}
public ViewModel CurrentViewModel
{
get { return _currentViewModel; }
private set
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
}
You can then amend your Xaml such that the frame gets it's data context from the CurrentViewModel property as follows:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" DataContext="{Binding CurrentViewModel}"/>
</StackPanel>
</Grid>
</Window>
And will then need to use some form of view location / navigation to change the frame to display the correct view. Some MVVM frameworks (for example, CaliburnMicro) can do this for you.
Again, in order to make this code testable, the instantiation of sub-viewmodels should be delegated to a factory class which is injected into the MainViewModel.
Hope it helps.
I have creating following GridRow as UserControl
<UserControl x:Class="Project.Telematics_Plugin.GridRow"
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" BorderBrush="LightBlue"
MaxHeight="30" MinWidth="900">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsChecked}" />
<TextBox Width="60" Text="{Binding EventId}"/>
<TextBox Width="300" Text="{Binding MethodName}" />
<ComboBox Width="200" ItemsSource="{Binding }" />
<ComboBox Width="200"/>
<ComboBox Width="200"/>
<Button Click="OnClickEdit">
<Image Source="Images/edit.png"/>
</Button>
<Button Click="OnClickDelete">
<Image Source="Images/delete.png"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
Here is the code behind
public partial class GridRow : UserControl
{
public bool IsChecked { get; set; }
public int EventId { get; set; }
public string MethodName { get; set; }
public string Level { get; set; }
public string Opcode { get; set; }
public string Task { get;set; }
public string Keyword { get; set; }
public GridRow()
{
InitializeComponent();
}
private void OnClickEdit(object sender, RoutedEventArgs e)
{
}
private void OnClickDelete(object sender, RoutedEventArgs e)
{
}
}
Now can you please tell what important thing I missed to bind properties of code behind files to UI in TwoWay Mode..
Although this is not the MVVM way..
Add an x:Name to your control and bind to the properties using ElementName:
<UserControl x:Name="MyGridRow">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=MyGridRow}" />
<TextBox Width="60" Text="{Binding EventId, ElementName=MyGridRow}"/>
<TextBox Width="300" Text="{Binding MethodName, ElementName=MyGridRow}" />
<ComboBox Width="200" ItemsSource="{Binding Path=., ElementName=MyGridRow}" />
<ComboBox Width="200"/>
<ComboBox Width="200"/>
<Button Click="OnClickEdit">
<Image Source="Images/edit.png"/>
</Button>
<Button Click="OnClickDelete">
<Image Source="Images/delete.png"/>
</Button>
</StackPanel>
</Grid>
</UserControl>
If you want to support updating the values, you should use DependencyProperties instead of normal properties:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(GridRow));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { GetValue(IsCheckedProperty, value); }
}
when the DataContext where you use your usercontrol has all the properties IsChecked, EventId,MethodName ,..., then you can remove the properties from your usercontrol and all works.
but if you wanna create a "real" usercontrol then you should use DependencyProperties and bind them with the right expression within your usercontrol.
btw when you use Binding in WPF then its all about the right DataContext and the right BindingExpression
I have a UserControl with several TextBox controls and a ProgressBar. The TextBox controls properly reflect the properties in codebehind to which they are bound. The ProgressBar does not respond to property change, however.
My XAML:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<Controls:BindablePasswordBox Password="{Binding DatabasePassword}" Height="23" Canvas.Left="160" Canvas.Top="96" Width="160"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
And its codebehind (very abbreviated):
public partial class MobileRecruiterModule : UserControl, INotifyPropertyChanged
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private int _progress;
public MobileRecruiterModule()
{
InitializeComponent();
DataContext = this;
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Logger.Trace("Progress.set() = " + _progress);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
Progress = args.ProgressPercentage;
}
}
I know the value of Progress is changing because I see it in the NLog logs:
2014-04-17 16:22:54.4068|TRACE|Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule|Progress.set() = 28
I don't understand why the ProgressBar doesn't update when I fire OnPropertyChanged in the setter just before the logging call.
I replicated a scaled down version of your app in an MVVM pattern and had good luck with it. I used this code to replicate your user control...
<UserControl x:Class="ProgressBarBinding.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<Label Content="Database Server" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox Text="{Binding DatabaseServer}" Height="23" Canvas.Left="160" Canvas.Top="12" Width="160"/>
<Label Content="Database Name" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="38"/>
<TextBox Text="{Binding DatabaseName}" Height="23" Canvas.Left="160" Canvas.Top="40" Width="160"/>
<Label Content="Database Username" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="66"/>
<TextBox Text="{Binding DatabaseUsername}" Height="23" Canvas.Left="160" Canvas.Top="68" Width="160"/>
<Label Content="Database Password" HorizontalAlignment="Left" VerticalAlignment="Top" Canvas.Left="10" Canvas.Top="94"/>
<ProgressBar Name="ProgressBar" Value="{Binding Progress}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
The only thing missing from that is your proprietary password control, which does not affect the solution.
I encoded this control into a MainWindow.xaml file thusly...
<Window x:Class="ProgressBarBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ProgressBarBinding"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:ViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<vm:Login/>
</Grid>
</Window>
Note that the window resource definition includes a reference to a view model instance. Most people set up MVVM with dependency injection, but this approach is good for quick trials and Indicative Code. The view model is set as the Grid's data context. Your control inherits the data context from the grid. That's the end of the xaml code. There is no code-behind in the MainWindow.xaml.cs file other than the call to InitializeComponent (and that's where the VM instance gets created).
The ViewModel class looks like this...
public class ViewModel : INotifyPropertyChanged
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public ViewModel()
{
DatabaseServer = "AnyServer";
DatabaseName = "Any name";
Model m = new Model();
Task.Run(() => m.DoWork(this));
}
public string DatabaseServer { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUsername { get; set; }
public string DatabasePassword { get; set; }
private int _progress;
public int Progress
{
get { return _progress; }
set
{
if (value == _progress) return;
_progress = value;
OnPropertyChanged("Progress");
Console.WriteLine(#"Progress.set() = " + _progress);
}
}
// This is called by an external class
public void OnProgressChanged(object sender, ProgressChangedEventArgs args)
{
_synchronizationContext.Send(delegate { Progress = args.ProgressPercentage; }, null);
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
Most of the code in the view model looks like yours except there are no dependencies on UI elements. Everything is done via binding. I used a SynchronizationContext in the callback, although it may not be necessary in your application.
The constructor of the VM starts a model on a TPL thread. The model looks like this...
public class Model
{
public void DoWork(ViewModel vm)
{
int progressPercentage = 0;
for (int i = 0; i < 100000; i++)
{
vm.OnProgressChanged(this, new ProgressChangedEventArgs(progressPercentage, null));
if (i%1000 == 0)
{
++progressPercentage;
}
}
}
}
So putting it all together, the model is running in its own thread, and the UI is being updated on its own thread. The whole thing works as expected.
The ProgressBar will increment its way up to 100 and the UI will remain responsive while the model is doing its work. This answer does not explain why your original code does not work, but I suspect it has to do with the UI thread being starved out. This is evidenced by your complete log history, but nothing changing on the UI. Overall, this answer moves toward what others have suggested in their commentary: namely that the MVVM approach of binding has a lot to offer.
Since you're in a UserControl, you need to explicitly give it a name and use the ElementName tag when binding, like this:
<UserControl
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:Controls="clr-namespace:Cmc.Installer.Controls;assembly=Cmc.Installer.Controls" x:Class="Cmc.Installer.Modules.MobileRecruiter.MobileRecruiterModule"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800" x:Name="MyControl">
<Grid HorizontalAlignment="Left" Height="580" Margin="10,10,0,0" VerticalAlignment="Top" Width="780">
<Canvas>
<ProgressBar Name="ProgressBar" Value="{Binding Progress, ElementName=MyControl}" Minimum="0" Maximum="100" Canvas.Left="10" Canvas.Top="164" Width="760" Height="24" />
</Canvas>
</Grid>
</UserControl>
Why do you need Binding if you have the Actual control. Since you are not doing it in MVVM just call the ProgressBar right away.
ProgressBar.Dispatcher.Invoke(() => ProgressBar.Value = Progress = args.ProgressPercentage);
Sorry but I don't see the benefit of the Binding if all the properties/controls are accessible in your View's class.
Binding is more useful and powerful if you implemented MVVM.
I am developing my first Windows 8 app, in one page i am trying to update button text with latest timestop when page loads. I defined my xaml and codebehind like below:
I am using databinding to update the button text but it is not working as expected:
MainPage.xaml
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Button.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</DataTemplate>
</Button.Resources>
<Button.ContentTemplate>
<StaticResource ResourceKey="DataTemplate1"/>
</Button.ContentTemplate>
</Button>
</Grid>
MainPage.xaml.cs
public StatsClass Stats { get; private set; }
public MainPage()
{
this.InitializeComponent();
this.DataContext = Stats;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
UpdateButton();
}
private void UpdateButton()
{
if (Stats == null)
Stats = new StatsClass();
Stats.ButtonText = DateTime.Now.ToString();
}
StatsClass.cs
public class StatsClass : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonText
{
get
{
return _buttonText;
}
set
{
_buttonText = value;
OnPropertyChanged("ButtonText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
You have set Content of your Button twice, once with Content="Button" and again with.Button.ContentTemplate. You could just have:
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</Button>
I had a similar issue yesterday using binding in a DataTemplate. I guess that you also had a binding error in the debug output.
I solved it using a relative source like that:
<TextBlock Text={Binding DataContext.ButtonText,
RelativeSource={RelativeSource FindAncestor, AncestorType=*YourControl*}}"/>
The Template has no direct access to the datacontext. By using the relative source you can bind to its properties.