I have an object called "Employee", as follows:
class Employee
{
public string EmpName { get; set; }
public string Surname { get; set; }
public Employee(string Name, string Surname) : base()
{
this.EmpName = EmpName;
this.Surname = Surname;
}
public Employee()
{
}
}
I also have this simple "EmployeeControl" here. Just two labels in a grid:
<UserControl x:Class="LabelBindingTest.EmployeeCtrl"
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"
Name="EmpCtrl"
d:DesignHeight="120" d:DesignWidth="411">
<Grid>
<Label Content="{Binding ElementName=EmpCtrl, Path=EmpName}" Height="28" HorizontalAlignment="Left" Name="label1" VerticalAlignment="Top" Width="81" />
<Label Content="{Binding ElementName=EmpCtrl, Path=Surname}" Height="28" HorizontalAlignment="Right" Name="label2" VerticalAlignment="Top" Width="81" />
</Grid>
</UserControl>
(Edit) EmployeeControl codebehind:
public partial class EmployeeCtrl : UserControl
{
Employee thisEmployee = new Employee();
public string EmpName
{
get
{
return thisEmployee.EmpName;
}
set
{
thisEmployee.EmpName = value;
}
}
public string Surname
{
get
{
return thisEmployee.EmpName;
}
set
{
thisEmployee.EmpName = value;
}
}
public EmployeeCtrl()
{
InitializeComponent();
}
}
Now, what I'm trying to do is add "EmployeeControl"s to my window and bind them to Employee objects.
Here's my "window" code:
<Window x:Class="LabelBindingTest.MainWindow"
Name="myWindow"
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:LabelBindingTest">
<Grid>
<my:EmployeeCtrl EmpName="Daniel" Surname="Hammond" HorizontalAlignment="Left" Margin="23,26,0,0" x:Name="employeeCtrl1" VerticalAlignment="Top" Height="97" Width="429" />
</Grid>
</Window>
I've tried a variety of things but I just can't get it to work. It compiles just fine but the label is empty. I just want to attach my "Employee" object to the "EmployeeCtrl" control. Any ideas on how I go around this? I've read a lot about binding today and I'm still scratching my head. I'm not sure if I need to implement INotifyPropertyChanged as I only set the employee names before runtime for now.
(Edit): I've downloaded Caliburn.Micro and used the same code in the last answer. Same result, empty window.
Here's the whole project. All of it's code should be pasted in this question, though. This is just for convenience sake.
http://www.mediafire.com/?gux3573rz64mupe
Ok, firstly I would strongly recommend considering MVVM as your application gets more complicated, and use an MVVM framework if you're using MVVM. I would recommend Caliburn.Micro which makes view composition like you're doing much much easier. There are other MVVM frameworks available, so evaluate them all.
For your issue, the problem is that your user control doesn't implement its properties as dependency properties. In the XAML, you're setting the EmpName and Surname properties of the user control, but the UI doesn't know that their values have changed after the user control is constructed, and therefore doesn't update itself.
You implement INotifyPropetyChanged on view models when using the MVVM pattern, and dependency properties on user controls. Both of these technqiues will notify the UI of a change and force it to update itself.
So for your user control you can do:
public partial class EmployeeCtrl : UserControl
{
public static readonly DependencyProperty EmpNameProperty =
DependencyProperty.Register("EmpName", typeof(string),
typeof(EmployeeCtrl), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty SurnameProperty =
DependencyProperty.Register("Surname", typeof(string),
typeof(EmployeeCtrl), new FrameworkPropertyMetadata(null));
public EmployeeCtrl()
{
this.InitializeComponent();
}
public string EmpName
{
get { return (string)GetValue(EmpNameProperty); }
set { SetValue(EmpNameProperty, value); }
}
public string Surname
{
get { return (string)GetValue(SurnameProperty); }
set { SetValue(SurnameProperty, value); }
}
}
You don't actually need your Employee class at all at this stage as you aren't using it, but ideally it should implement INotifyPropertyChanged if it's property values will change after construction and you have an instance of it is bound to the UI and you wish the UI to be updated.
you need to implement INotifyPropertyChanged on your EmployeeCtrl's EmpName and Surname property as the Label in this user control needs to be updated about the value change in this property value so that label can update its value. Hence you have to implement Notify change on EmpName and Surname property of the EmployeeCtrl or simply use dependency property for this two property. Hope this helps!!!
Related
Using data binding to get value from another UserControl when the button is clicked
So, I have a UserControl that is nested in another UserControl that is also nested in the main window, that looks like this,
MainWindow.xaml:
<Window
xmlns:view="clr-namespace:InvoiceAppV2.View"
<Grid>
<TabControl>
<TabItem>
<view:InvoiceControl>
</TabItem>
<TabItem/>
<TabItem/>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
InvoiceControl xaml:
<UserControl
xmlns:view="clr-namespace:InvoiceAppV2.View"
<Grid>
<view:BuyerInfoControl/>
</Grid>
<Button x:Name="btnSubmit" Content="Submit" Click="BtnSubmit_Click"/>
</UserControl>
InvoiceControl.xaml.cs
public partial class InvoiceControl : UserControl
{
Buyer b = new Buyer();
public InvoiceUserControl()
{
InitializeComponent();
}
private void BtnSubmit_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(b.BuyerName);
}
}
BuyerInfoControl xaml:
<UserControl
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Buyer's Name"/>
<Label Content="Purchase Date"/>
<TextBox x:Name="txtBuyerName" Text="{Binding Path=BuyerName, Mode=TwoWay}"
TextWrapping="NoWrap"/>
<DatePicker x:Name="txtPurchaseDate" Text="{Binding Path=PurchaseDate, Mode=TwoWay}" />
</Grid>
Here's the code to handle the property change
public class Buyer: INotifyPropertyChanged
{
private int _id;
private string _name;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public int ID
{
get { return _id; }
set { _id = value; }
}
public string BuyerName
{
get { return _name; }
set
{
if(_name != value)
{
_name = value;
PropertyChanged(this, new PropertyChangedEventArgs("BuyerName"));
}
}
}
public Buyer() {}
public Buyer(int id, string name)
{
ID = id;
BuyerName = name;
}
public Buyer(string name)
{
ID = 0;
BuyerName = name;
}
}
xaml.cs
public partial class BuyerInfoControl : UserControl
{
Buyer b;
public BuyerInfoControl()
{
InitializeComponent();
b = new Buyer(txtBuyerName.Text);
this.DataContext = b;
}
}
when a value of "John Doe" is typed in the TextBox and the button is clicked, the buyer's name is null. I usually work in swfit, object-c and MVC. Trying to figure how to play with MVVM and WPF. I have a feeling I'm doing some wrong here.
Here's the design
b = new Buyer(txtBuyerName.Text); only gets the last reference to the state of Text; meaning that it is a snapshot.
To achieve proper binding, it has to occur on the main page where the custom control resides and like the TextBox it will need to bind to the source string. To do that one must provide a Dependency Property on the custom control.
For example, if a dependency property named MyText is added to the control bind like this:
<InvoiceControl MyText="{Binding Path=BuyerName}"/>
<TextBox x:Name="txtBuyerName" Text="{Binding Path=BuyerName, Mode=TwoWay}"
TextWrapping="NoWrap"/>
Since InvoiceControl is a user control, the user control on the main page has to also be bound to the originating BuyerName (as shown) and to do what you want it has to happen via a dependency property put on InvoiceControl.
Wire up a dependency property on InvoiceControl as such:
#region public string MyText
/// <summary>
/// This the dependency property for the control.
/// </summary>
public string MyText
{
get { return GetValue(MyTextProperty) as string; }
set { SetValue(MyTextProperty, value); }
}
/// <summary>
/// Identifies the MyText dependency property.
/// </summary>
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register(
"MyText",
typeof(string),
typeof(InvoiceControl),
new PropertyMetadata(string.Empty));
#endregion public string MyText
then load like this
MessageBox.Show(MyText);
See Dependancy Property Overview
Also there may be an issue where you are trying to do the button click, right after typing into the control and the proper change event doesn't fire.
In the TextBox put in the binding UpdateSourceTrigger=PropertyChanged on the so that every text change sends a notification message. That way if one types in and focus moves to the button, the two way binding notification will fire.
Note if you work with custom controls this link has Visual Studio snippets which put in fillable templates for dependency properties in the editor. Ignore the title with Siverlight and opy the snippets to C:\Users\{You}\Documents\Visual Studio 20{XX}\Code Snippets\Visual C#\My Code Snippets, for they will work with all versions of visual studio:
Helpful Silverlight Snippets - Jeff Wilcox
The ViewModel:
public class ConnectionStatusViewModel : BindableBase
{
private string _txtConn;
public string TextConn
{
get { return _txtConn; }
set { SetProperty(ref _txtConn, value); }
}
}
The XAML:
<UserControl x:Class="k7Bot.Login.Views.ConnectionStatus"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
prism:ViewModelLocator.AutoWireViewModel="True" Width="300">
<Grid x:Name="LayoutRoot">
<Label Grid.Row="1" Margin="10,0,10,0">connected:</Label>
<TextBlock Text="{Binding TextConn}" Grid.Row="1" Grid.Column="1" Margin="10,0,10,0" Height="22" />
</Grid>
</UserControl>
The View:
public partial class ConnectionStatus : UserControl
{
public ConnectionStatus()
{
InitializeComponent();
}
}
In another module, I have an event listener, that eventually runs this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
The code runs but the TextConn is updated and does not display in the UI
Are you sure TextConn does not update? Because it can update but the display could not change. You should implement the INotifyPropertyChanged interface and after you make any changes to TextConn call the implemented OnPropertyChanged("TextConn"); or whatever you name the function. This will tell the UI that the value has changed and it needs to update.
The UserControl's DataContext gets its value when the UC is initialized. Then you get a copy of the DataContext, cast it to a view model object, and change the property. I don't believe that the UC gets its original DataContext updated in this scenario.
Probably you need to use a message mediator to communicated changes between different modules.
After some troubleshooting, this code works, the issue was that I was running this code:
ConnectionStatusViewModel viewModel = _connectionView.DataContext as ConnectionStatusViewModel;
if (viewModel != null)
{
viewModel.TextConn = "Testing 123";
}
before the view was actually activated. Silly, but maybe it will help someone down the line.
All, I have a custom DataGridView control which overrides the DataGidView's OnItemsSourceChanged event. Inside this event I need to get a reference to a data set in the relevant ViewModel. Code is
public class ResourceDataGrid : DataGrid
{
protected override void OnItemsSourceChanged(
System.Collections.IEnumerable oldValue,
System.Collections.IEnumerable newValue)
{
if (Equals(newValue, oldValue))
return;
base.OnItemsSourceChanged(oldValue, newValue);
ResourceCore.ResourceManager manager = ResourceCore.ResourceManager.Instance();
ResourceDataViewModel resourceDataViewModel = ?? // How do I get my ResourceDataViewModel
List<string> l = manger.GetDataFor(resourceDataViewModel);
...
}
}
On the marked line I want to know how to get a reference to ResourceDataViewModel resourceDataViewModel. The reson is that i have multiple tabs each tab contains a data grid and ascociated ViewModel, the ViewModel holds some data that I need to retrieve [via the ResourceManager] (or is there another, better way?).
The question is, from the above event, how can I get the ascociated ResourceDataViewModel?
Thanks for your time.
Get the DataContext and cast it to the view-model type:
var viewModel = this.DataContext as ResourceDataViewModel
Put a static reference to it on your app, when the VM is created place its reference on the static and access it as needed.
You ask if there is a better way... In my experience if you find yourself subclassing a UI element in WPF there ususally is.
You can get away from embedding business logic (the choice of which data to display in the grid), by databinding your entire tab control to a view model.
To demonstrate - here is a very simple example. This is my XAML for the window hosting the tab control:
<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">
<Grid>
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabName}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<DataGrid ItemsSource="{Binding TabData}"></DataGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The data context of my window is a TabsViewModel (I am using the NotificationObject that can be found in the PRISM NuGet Package):
public class TabsViewModel: NotificationObject
{
public TabsViewModel()
{
Tabs = new[]
{
new TabViewModel("TAB1", "Data 1 Tab 1", "Data 2 Tab1"),
new TabViewModel("TAB2", "Data 1 Tab 2", "Data 2 Tab2"),
};
}
private TabViewModel _selectedTab;
public TabViewModel SelectedTab
{
get { return _selectedTab; }
set
{
if (Equals(value, _selectedTab)) return;
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
public IEnumerable<TabViewModel> Tabs { get; set; }
}
public class TabViewModel
{
public TabViewModel(string tabName, params string[] data)
{
TabName = tabName;
TabData = data.Select(d => new RowData(){Property1 = d}).ToArray();
}
public string TabName { get; set; }
public RowData[] TabData { get; set; }
}
public class RowData
{
public string Property1 { get; set; }
}
This is obviously an over simplified case, but it means that if there is any business logic about precisely what data to show in each tab, this can reside in one of the view models, as opposed to the code behind. This gives you all the 'separation of concerns' benefits that MVVM is designed to encourage...
The files I have created and will be referring to in this question are:
TechnicainSelectionView.xaml
TechnicianSelectionView.cs
TechnicianSelectionViewModel.cs
Technician.cs (Code First Entity)
I have the following xaml in my TechnicanSelectionView.xaml
<UserControl xmlns etc... here"
d:DesignHeight="48" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="Select a Technican to run the test" FontWeight="Bold"></Label>
<ComboBox ItemsSource="{Binding Technicians, Mode=TwoWay}"></ComboBox>
</StackPanel>
</Grid>
</UserControl>
The Technicians property to which the ItemSource is set to bind to states that it Cannot resolve Technicians due to an unknown DataContext.
So if we look to my TechnicianSelectionView.cs code-behind...
public partial class TechnicianSelectionView : UserControl
{
public TechnicianSelectionViewModel ViewModel { get; private set; }
public TechnicianSelectionView()
{
InitializeComponent();
Technician.GenerateSeedData();
ViewModel = new TechnicianSelectionViewModel();
DataContext = ViewModel;
}
}
... we see that I am setting the view's DataContext to my TechnicianSelectionViewModel ...
public class TechnicianSelectionViewModel : ViewModelBase
{
public ObservableCollection<Technician> Technicians { get; set; }
public TechnicianSelectionViewModel()
{
Technicians = new ObservableCollection<Technician>();
}
public bool IsLoaded { get; private set; }
public void LoadTechnicians()
{
List<Technician> technicians;
using (var db = new TestContext())
{
var query = from tech in db.Technicians
select tech;
foreach (var technician in query)
{
Technicians.Add(technician);
}
}
IsLoaded = true;
}
}
Techicians is a property on my ViewModel...
So having already set the DataContext for the view, why can't it resolve Technicians on the ViewModel as the DataContext/property it is going to bind to?
EDIT:
As per a concern in a comment below. This is a design time problem and not compile time. I should have indicated this at the start.
You need to specify the type of data context in the xaml to get design-time support. Even though you assigned the data context in code-behind, the designer is not going to recognize that.
Try putting the following in your xaml:
d:DataContext="{d:DesignInstance vm:TechnicianSelectionViewModel}"
See this link for more details.
In my Xamarin Forms Xaml file I used the following lines in the header (ContentPage tag) and it worked perfectly as I wanted.
Basically now
the intellisense shows the fields in the binding
my Resharper is able to rename the binding in the Xaml file if I refactor the name of the property
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:YourApplicationName.ViewModels;assembly=YourApplicationName"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type vm:CurrentPageViewModel}}"
I am trying my hands on WPF MVVM. I have written following code in XAML
<UserControl x:Class="Accounting.Menu"
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:Accounting"
mc:Ignorable="d"
d:DesignHeight="105" d:DesignWidth="300">
<UserControl.DataContext>
<local:MenuViewModel/>
</UserControl.DataContext>
<StackPanel>
<StackPanel>
<TextBlock Text="{Binding Path=MenuHeader}"/>
</StackPanel>
<ListBox ItemsSource="{Binding Path=MenuItems}" Height="70"/>
</StackPanel>
</UserControl>
I have got a MenuViewModel with properties MenuHeader and MenuItems. I get values in both the properties during runtime. Former is bound to text of TextBlock and latter to ItemSource of ListBox. But when I run the solution, TextBlock and ListBox are empty.
Edit: Code of ViewModel
public class MenuViewModel: ViewModelBase
{
AccountingDataClassesDataContext db;
private string _menuType;
public string MenuHeader { get; set; }
public ObservableCollection<string> MenuItems { get; set; }
public MenuViewModel()
{
}
public MenuViewModel(string menuType)
{
this._menuType = menuType;
db = new AccountingDataClassesDataContext();
if (menuType == "Vouchers")
{
var items = db.Vouchers.OrderBy(t => t.VoucherName).Select(v => v.VoucherName).ToList<string>();
if (items.Any())
{
MenuItems = new ObservableCollection<string>(items);
MenuHeader = "Vouchers";
}
}
else
{
System.Windows.MessageBox.Show("Menu not found");
}
}
}
Thanks in advance.
You are creating your ViewModel in the XAML using your ViewModel's default contructor which does nothing. All your population code is in the non-default contructor which is never called.
The more usual way is to create the ViewModel in code, and inject it into the view either explicitly using View.DataContext = ViewModel, or impllcitly using a DataTemplate.
I think you have to trigger the OnPropertyChanged event. I am not sure if you are using a MVVM library (since you inherit from ViewModelBase you might be using MVVM Light for example), there they wrap the OnPropertyChanged in the RaisePropertyChanged event handler.
Triggering the event will inform WPF to update the UI.
string m_MenuHeader;
public string MenuHeader
{
get
{
return m_MenuHeader;
}
set
{
m_MenuHeader=value; OnPropertyChanged("MenuHeader");
}
}