Bind a WPF View to couple of DataContexts - c#

Objective: Set the visibility of a control based on the selected value of a ComboBox
Issue: The property that is being used to check the visibility is in the VM, however I don't know how to use it as DataContext is already defined to another object, i.e would I need to bind 2 datacontexts?!
Details:
I have a CustomControl that I load in my view associating to it a DataContext (a List of objects that is displayed as a grid:
<GUI:Counterparties_UserInputs x:Name="UserInputs" DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource counterpartiesDataView}}"/>
In that user control I have some StackPanel which visibility should be triggered based on the selection of a ComboBox:
<ComboBox ItemsSource="{Binding Source={StaticResource CounterpartyTypes}}" SelectedValue="{Binding SelectedCounterpartyType}"/>
<StackPanel Visibility="{Binding Path=SelectedCounterpartyType,Converter={StaticResource SelectedValueToVisible}}"/>
The issue I have is that the code behind is never hit as I don't find how to associate an "extra" DataContext to the view.
Here is my code behind:
public partial class Counterparties_UserInputs : UserControl
{
...
public Counterparties_UserInputs()
{
// this.DataContext = _cptyUserInputsVM;
_cptyUserInputsVM = new Counterparties_UserInputs_VM();
InitializeComponent();
}
}
And the ViewModel where the Property "SelectedCounterpartyType" is never hit:
public class Counterparties_UserInputs_VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _selectedCounterpartyType;
public string SelectedCounterpartyType
{
get
{
return _selectedCounterpartyType;
}
set
{
_selectedCounterpartyType = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCounterpartyType"));
}
}
}
}
I've seen that answer already but it's not exactly what I am doing... So would really appreciate your help! Thank you!

Related

Binding model with multiple properties in UserControl using one DependencyProperty [duplicate]

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.
Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.
Model:
public class MyModel : INotifyPropertyChanged
{
private string _surname;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Surname
{
get => _surname;
set
{
_surname = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyModelEditor.xaml (inside Grid):
<DockPanel>
<TextBox Text="{Binding MyModel.Name}"/>
<TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>
Also contains this line in UserControl root element:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
MyModelEditor.xaml.cs:
public partial class MyModelEditor : UserControl
{
public MyModel MyModel
{
get => (MyModel)GetValue(MyModelProperty);
set => SetValue(MyModelProperty, value);
}
public static readonly DependencyProperty MyModelProperty =
DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));
public MyModelEditor()
{
InitializeComponent();
}
}
MainWindow.xaml (inside Grid):
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
<controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private MyModel _myModel;
public MyModel MyModel
{
get => _myModel;
set
{
_myModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(MyModel?.Name);
}
}
My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.
I wold not like to bind to all properties separately, because in future I will have much more then two properties.
Why current approach does not work?
How can I achieve my goal?
You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:
<TextBox Text="{Binding MyModel.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like
<controls:MyModelEditor DataContext="{Binding MyModel}"/>
The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:
<DockPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Surname}"/>
</DockPanel>
For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):
<DockPanel>
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
<TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>
As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).
Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel
EDIT: Fixes are in your GitHub repo
Two things to note here
You have not instantiated your ViewModel (i.e. MyModel), so it was always null
You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself

WPF databinding not working despite the target datacontext bound to the correct source memory address

The scenario is very simple here. I'm trying to bind a textbox to a property of a class at runtime:
tb.displayValue.DataContext = p.GetValue(currentNode, null);
xaml for the textbox:
<TextBox Name="displayValue" Grid.Column="1"
Style="{StaticResource propertyTextBoxStyle}"
Text="{Binding Path=DataContext,
RelativeSource={RelativeSource Self},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
</TextBox>
When the application starts, the textbox does get populated with the correct information so the source to target binding is working fine. However, when I try and edit a value in the textbox and then switch focus, the changes are not reflected in the actual data structure. The value would stay on the UI, but as soon as I try to reload the UI from the data structure again it defaults back to the original value.
I suspect the binding is not working correctly at first, but after checking the memory address of tb.displayValue.DataContext and comparing it to the actual memory address of the data structure it's an identical match.
INotifyPropertyChanged has been implemented and I have added the OnPropertyChanged call to every setter. After spending two days trying to debug this issue I think I'm really running out of options here so any suggestion would be appreciated.
The simplest two-way binding works this way: you set the DataContext on your Window to a new instance of your MainWindowViewModel class which implements INotifyPropertyChanged, and you set the binding path on your TextBox to the name of the public property on your ViewModel you want to bind to.
I'm trying to show how you need a public property with a get and set to bind to, and how to properly set the DataContext for your window so that all of the controls within it are able to bind to the public properties available on it.
I've never heard of setting the DataContext of a TextBox directly to the return value from a method before, and it just seems wrong, so maybe you are not going about it the right way, and hopefully this helps you see how it can work.
MainWindow.cs
<Window x:Class="DemoWPFApp1.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:vm="clr-namespace:DemoWPFApp1.ViewModels"
Height="300" Width="460" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<TextBox Name="displayValue" Text="{Binding Path=BoundProperty,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</Window>
MainWindowViewModel.cs
namespace DemoWPFApp1.ViewModels
{
public class MainWindowViewModel : BaseViewModel
{
private string m_boundProperty;
public string BoundProperty
{
get
{
return m_boundProperty;
}
set
{
m_boundProperty = value; OnPropertyChanged();
}
}
public MainWindowViewModel()
{
BoundProperty = "Some value.";
}
}
}
BaseViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DemoWPFApp1.ViewModels
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propName = null)
{
var e = PropertyChanged;
if (e != null && propName != null)
{
e.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
}

Show chosen menu item

Hy,
I have a menu with a few menu items. I have various other elements like a treeview and some controls. When I open the program all elements in the menu are available. But the first step I have to do is to connect to the server. So all the the other elements shouldn't available till there is made a connection via the connection menu item.
Then I want to show only menu items if a special tree view (for instance the whole item structure) item is choosen for instance all topics. For instance there should be special menu items available if I click a treeview entry in the menu.
Is it possible to accomplish this in xaml?
Update1:
MainWindow.xaml
Title="Service Bus Visualizer" Height="680" Width="1200" Name="Root"
<MenuItem Header="_Read File" Name="readFile" Click="MenuItemReadFile" HorizontalAlignment="Right" Width="187" IsEnabled="{Binding Path=DataContext.IsMonitoring, ElementName=Root}">
<MenuItem.Icon>
<Image Source="Icons/Open.ico" Width="16" Height="16" />
</MenuItem.Icon>
</MenuItem>
MainWindow.xaml.cs
public bool IsMonitoring
{
get
{
return isMonitoring;
}
set
{
isMonitoring = value;
RaisePropertyChanged("IsMonitoring");
}
}
private bool isMonitoring;
public MainWindow()
{
InitializeComponent();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
ConnectionWindow.xaml.cs
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
mainWindow.IsMonitoring = true;
I get no error on the output window but it doesn't work?
Update2:
I have a second parameter which is a ObservableCollection.
MainWindow.xaml
<ListBox Grid.Row="3" Name="Logger" ItemsSource="{Binding Path=DataContext.LoggingList, ElementName=Root}" DisplayMemberPath="Message" IsSynchronizedWithCurrentItem="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="BringSelectionIntoView">
</ListBox>
MainWindow.xaml.cs
public static ObservableCollection<Log> LoggingList { get; set; }
public MainWindow()
{
LoggingList = new ThreadSafeObservableCollection<Log>();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
InitializeComponent();
}
Log.cs
public class Log : INotifyPropertyChanged
{
public string Message {
get
{
return message;
}
set
{
message = value;
NotifyPropertyChanged("Message");
}
}
public string message;
public Log()
{
}
public Log(string message)
{
this.Message = message;
NotifyPropertyChanged("Message");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Best regards
First, you have two options as far as "availability" is concerned. The "IsEnabled" property, and the "Visible" property. "IsEnabled" is a bool, and determines if the user can click/select/interact with a given element. Generally speaking, if this property is set to false the element will appear "greyed out".
Changing Visibility will make the element appear/disappear entirely. When set to "Visible" (this is actually an enum), it appears normally. When set to "Hidden", the space for it is reserved on the UI, but you can't actually see it. When set to "Collapsed" you cannot see it and no space is reserved for it in the layout.
For your first requirement (waiting to connect to the server), I would use IsEnabled bound to a "IsConnected" property like so:
IsEnabled="{Binding IsConnected}"
That would go on each item that needs to have this behavior.
The "context-specific" menu items are a bit more complicated, but the basic idea would be a binding on Visible for each of the context sensitive items like:
Visible="{Binding Path=SelectedItem, ElementName=MyTreeView, Converter={StaticResource SelectedItemToVisibilityConverter}, ConverterParameter={x:Type ChildItem}"
I am assuming that each items visibility depends on what type of item is selected (child or parent), you should be able to extend the example if I was wrong. The converter would then look like:
public class SelectedItemToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((parameter as Type).IsInstanceOfType(value))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
Please let me know if I can clarify anything. Hopefully that gives you a good starting point for what you are trying to do.
Update:
Looking at your code I see a couple potential problems:
IsMonitoring is declared as a public field. Binding only works with public properties. This property needs to raise the PropertyChanged event for it to work.
In "MainWindow.xaml.cs" you are setting the DataContext multiple times. This isn't how DataContext works in WPF. You need to set it to one object (your ViewModel) that contains all the properties you are interested in binding to. While it is considered bad practice, you could write this.DataContext = this to get it working before you build a ViewModel class.
The IsMonitoring field is declared in your "MainWindow.xaml.cs" file. First, this should be in a view model. Second, the binding is looking for that property on the MenuItem class (likely because it is in some sort of ItemsControl). If you want it on the root data context, give your window some name (like "Root") and use the following binding:
"{Binding Path=DataContext.IsMonitoring, ElementName=Root}"
Hopefully that makes sense. Let me know if I can help further!

How to bind to one element of an ObservableCollection

I have an ObservableCollection and want to bind a Textbox to a specific element of that collection. The Items in the ObservableCollection are of a Type that implements INotifyPropertyChanged.
I have thought about creating a Property that selects the right element from the ObservableCollection, but then I would have to make this Property realise when the corresponding element in the Collection changes and I am not sure if this is the right way to do this.
Usually, especially if you use MVVM, you'll have a viewModel with your ObservableCollection and a property for the SelectedItem that you update with data binding.
For example, your viewModel could look like this:
class ProductsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Product> Products { get; set; }
private Product _selectedProduct;
public Product SelectedProduct
{
get { return _selectedProduct; }
set
{
_selectedProduct = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
}
}
public ProductsViewModel()
{
Products = new ObservableCollection<Product>();
Products.Add(new Product() { Name = "ProductA" });
Products.Add(new Product() { Name = "ProductB" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
Your window object xaml:
<Window x:Class="ProductsExample.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>
<ListBox HorizontalAlignment="Left" Height="171" Margin="32,29,0,0" VerticalAlignment="Top" Width="176"
ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
DisplayMemberPath="Name"
/>
<TextBox HorizontalAlignment="Left" Height="33" Margin="36,226,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="172"
Text="{Binding SelectedProduct.Name, Mode=TwoWay}"/>
</Grid>
</Window>
and the code-behind where you just set the datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProductsViewModel();
}
}
Whenever you select a product in the listbox, the textbox is updated with the selected product, and if you change the product in the textbox (if product correctly implements INotifyPropertyChanged) the item in the listbox will also be updated.
Obviously you can achieve all this only using the code-behind, but for several reasons explained here:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx, it is better to have a ViewModel
If the item you need is specific by index you can access using the index
<TextBlock Text="{Binding MyItemsSource[2]}" />
To solve my problem I created a Property that selects the right element from the ObservableCollection and created an event Handler that is added to the CollectionChanged event of the ObservableCollection and raises the PropertyChanged Event for my SelectionProperty.
In code that looks something like this in the constructor of the class containing the ObservableCollection and the SelectionProperty:
myObservableColleciton.CollectionChanged +=
new NotifyCollectionChangedEventHandler(
myObservableCollection_CollectionChanged);
somewhere else in the class define this event handler:
void myObservableCollection_CollectionChanged(
Object sender, NotifyCollectionChangedEventArgs e){
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectionProperty"));
}
}
my selectionProperty looks something like this:
public User SelectionProperty
{
get { return myObservableCollection.First( user => user.id == 0); }
}
if the SelectionProperty depends on more than the ObservableCollection (maybe we want to find a user closest to a certain age, that is set elsewhere) then it needs to be made sure that the PropertyChanged event for SelectionProperty is raised as well, when those other properties change.

ObservableCollection or List data bind with comboBox using MVVM?

I have ObservableCollection items i want to bind this data to my comboBox.
How to bind data with comboBox?
I am using MVVM pattern so suggest me how to bind data using MVVM pattern
I am trying to do this code but not working properly..
In my XAML PAGE:
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=combodata}">
</ComboBox>
In my ViewModel
(Card is my model)
public ObservableCollection<Card> combodata = new ObservableCollection<Card>();
foreach (var item in App.db.States)
{
Card c = new Card(item.StateName, item.StateID);
combodata.Add(c);
}
How to bind this combodata to my comboBox - what am I doing wrong ?
At first: your combodata has private access modifier instead of public. At second: combodata must be property but not the field. And you'd better add INotifyPropertyChanged implementation to you class.
You have to bind to a public property of the view model that should implement INotifyPropertyChanged.
Here's what you should do:
View:
<ComboBox x:Name="comobo1" DisplayMemberPath="CardTypeName" SelectedValuePath="CardTypeID" ItemsSource="{Binding Path=ComboData}" />
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<Card> comboData;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Card> ComboData
{
get
{
return this.comboData;
}
set
{
if (this.comboData != value)
{
this.comboData = value;
this.NotifyPropertyChanged("ComboData");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EDIT: you'll also need to set the DataContext property of your view. The simple way is to affect the instance of your ViewModel to the DataContext property in the code-behind of your view.
private ObservableCollection<Card> _combodata;
Public ObservableCollection<Card> comboData
{
get
{
if (_combodata == null)
_combodata = new ObservableCollection<Card>();
return _combodata;
}
set
{
if (value != _combodata)
_combodata = value;
}
}
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=comboData}">
</ComboBox>
And don't forget to set a DataContext property.
There are following problems with the code.
1.You can not bind to private field or property. It should be public property.
2.You only provided ItemsSource="{Binding Path=combodata}" but did not provide a source. Where does combodata come from ?
ObservableCollection has nothing to do unless your combo box items doe not change once filled. In this case List can work well.
To make it work, Change your combodata to public property as
public ObservableCollection<Card> combodata {get;set;}
then,
<ComboBox x:Name="comobo1"
DisplayMemberPath="CardTypeName"
SelectedValuePath="CardTypeID"
ItemsSource="{Binding Path=combodata}" ElementName=mainWindow>
</ComboBox>
by specifying ElementName you are telling WPF binding engine to look for the combodata property of the mainWindow class.
I hope this helps.
My best guess is your ComboBox's DataContext is not set to an instance of your ViewModel
I often use Snoop to debug DataContext problems with the application. It allows you to view your Visual Tree, and see what the DataContext is for all controls.
The DataContext is the data your UI is bound to. Usually the DataContext is set higher up in the Visual Tree, such as on the Window object, although as an example the following line of code would set the ComboBox's DataContext to a new instance of your ViewModel, and then your ComboBox should be able to find the combodata collection to bind to it.
comobo1.DataContext = new MyViewModel();
Also, change your combodata from a Field (no get/set accessor methods) to a Property (see Dmitriy's Answer for an example)
You need to bind to a public property. In your example, combodata is private.
Correct Answer is :
In XAML PAGE":
<CollectionViewSource x:Key="comboBoxCollection" Source="{Binding comboData}"></CollectionViewSource>
<DataTemplate x:Key="ComboBoxDataTemplate">
<Grid MinHeight="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comobo1" DisplayMemberPath="CardTypeName" SelectedValuePath="CardTypeID" ItemsSource="{Binding Source={StaticResource comboBoxCollection }}">
</ComboBox>
</Grid>
</DataTemplate>
In ViewModel:
private ObservableCollection<Card> _combodata;
public ObservableCollection<Card> comboData
{
get
{
if (_combodata == null)
_combodata = new ObservableCollection<Card>();
return _combodata;
}
set
{
if (value != _combodata)
_combodata = value;
}
}
if (_objCardField.FieldTag == "State")
{
cards = new Cards();
foreach (var item in App.db.States)
{
Card c = new Card(item.StateName, item.StateID);
comboData.Add(c);
}
}

Categories