I have a UserControl 'UserControlA' with ViewModel 'ViewModelA'.
'UserControlA' has 'UserControlB', and 'UserControlB' has 'ViewModelB'.
When I bind a DependencyProperty in 'UserControlA' with 'ViewModelA' property,
there is none of setter fired.
Belows are code,
ViewA.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:vm="clr-namespace:MyTest.ViewModel
xmlns:custom="clr-namespace:MyTest.Views
x:Name="userControl" x:Class="MyTest.Views.UserControlA"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<UserControl.DataContext>
<vm:UserViewModel x:Name="uvModel"/>
</UserControl.DataContext>
<Grid>
<custom:UserControlB></custom:UserControlB>
ViewA.cs
public partial class UserView : UserControl, IUserView
{
static DependencyProperty UserTypeProperty = DependencyProperty.Register("UserType", typeof(UserType), typeof(UserView), new PropertyMetadata(UserType.None));
public UserType UserType { get { return (UserType)GetValue(UserTypeProperty); } set { SetValue(UserTypeProperty, value); } }
public ViewA()
{
InitializeComponent();
Binding typeBinding = new Binding();
typeBinding.Source = this.DataContext;
typeBinding.Path = new PropertyPath("User.UserType");
typeBinding.Mode = BindingMode.OneWayToSource;
this.SetBinding(UserTypeProperty, typeBinding);
}
ViewModelA.cs
public class ViewModelA : ViewModelBase
{
User user = new User();
public User User
{
get { return this.user; }
set
{
this.user = value;
RaisePropertyChanged(() => User);
}
}
Please help me out from this problem.
The line
typeBinding.Source = this.DataContext;
is redundant, because the DataContext is implicitly used as source object of the Binding.
However, during the execution of the UserControl's constructor the DataContext property is not yet set (i.e. it is null), so you are effectively setting the Binding's Source property to null. Just remove that line, or write
SetBinding(UserTypeProperty, new Binding
{
Path = new PropertyPath("User.UserType"),
Mode = BindingMode.OneWayToSource
});
Related
Problem
A user WPF control is made up of multiple standard controls.
How can multiple dependency properties of the component (base or standard) controls be accessed in XAML, when implementing the parent (user) control, without creating additional properties?
Details
What do I mean by "creating additional dependency properties"? Well, that is the only way I know of accessing properties of the component controls: by implementing attached properties, as described at MSDN here.
However, it presents the following problems:
Existing dependency properties must be copied as new properties, defeating the DRY principle.
If data binding is to occur, more work must be done to bind existing dependency properties to the new exposed dependency properties.
I'm wondering if there is a way to "walk" the base controls within the user control, to access their properties - from within XAML.
Example
For example, I make a user WPF control that inherits from UserControl. It is simple - it consists of a StackPanel containing a Label and a TextBlock:
<UserControl x:Class="MyApp.CustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Label Name="BaseLabel">Label Here</Label>
<TextBlock Name="BaseTextBlock">Some text here.</TextBlock>
</StackPanel>
</UserControl>
Now, when I use my UserControl elsewhere in XAML, I'm wishfully thinking something like this could be done to edit my Label's content... although I don't know of a way:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">
<StackPanel>
<!-- This won't work, don't try at home kids. -->
<local:CustomControl BaseLabel.Content="I did it!"></local:CustomControl>
</StackPanel>
</Window>
Much thanks.
How about the next solution:
1. Create the AttachedProperty (because you must an entry point) and bind this property to the collection of data.This collection of data will contain changes you want perform on sub-controls of a main user control used inside the window. This collection will be defined inside the main window view model.
2. In attached property changed callback get the binded collection, parse it data into sub-controls properties.
Here is the solution:
3. Xaml code:
<Window.DataContext>
<nirHelpingOvalButton:MainWindowViewModel />
</Window.DataContext>
<Grid>
<nirHelpingOvalButton:InnerControl x:Name="MyInnerControl"
nirHelpingOvalButton:Helper.InnerControlPropertiesAccessor="{Binding InnerData, Mode=Default, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
4. Attached property code (bindig support):
public static readonly DependencyProperty InnerControlPropertiesAccessorProperty = DependencyProperty.RegisterAttached(
"InnerControlPropertiesAccessor", typeof (ObservableCollection<TargetControlData>), typeof (Helper), new PropertyMetadata(default(ObservableCollection<TargetControlData>), InnerValueAccessProviderPropertyChangedCallback));
public static void SetInnerControlPropertiesAccessor(DependencyObject element, ObservableCollection<TargetControlData> value)
{
element.SetValue(InnerControlPropertiesAccessorProperty, value);
}
public static ObservableCollection<TargetControlData> GetInnerControlPropertiesAccessor(DependencyObject element)
{
return (ObservableCollection<TargetControlData>) element.GetValue(InnerControlPropertiesAccessorProperty);
}
private static void InnerValueAccessProviderPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as Control;
if (control == null) return;
var valuesMap = args.NewValue as ObservableCollection<TargetControlData>;
if (valuesMap == null)
return;
valuesMap.ToList().ForEach(data => TryToBind(control, data));
}
private static void TryToBind(Control control, TargetControlData data)
{
var innerControl = control.FindName(data.SubControlName) as DependencyObject;
if (innerControl == null) return;
var myBinding = new Binding
{
Source = data,
Path = new PropertyPath("Data"),
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
var descriptors = TypeDescriptor.GetProperties(innerControl);
var propertyDescriptor = descriptors.Find(data.SubConrolProperty, true);
var descriptor = DependencyPropertyDescriptor.FromProperty(propertyDescriptor);
if (descriptor == null) return;
var dependencyProperty = descriptor.DependencyProperty;
BindingOperations.SetBinding(innerControl, dependencyProperty, myBinding);
}
5. Inner control xaml:
<UserControl x:Class="NirHelpingOvalButton.InnerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UniformGrid>
<Button x:Name="InnerControlButton"></Button>
<TextBlock x:Name="InnerContentTextBlock"></TextBlock>
</UniformGrid>
6. ViewModel code:
public class MainWindowViewModel:BaseObservableObject
{
private static int _staticCount = 0;
private List<Brush> _list = new List<Brush> {Brushes.Green, Brushes.Red, Brushes.Blue};
public MainWindowViewModel()
{
InnerData = new ObservableCollection<TargetControlData>
{
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Content",
Data = "Click Me",
},
new TargetControlData
{
SubControlName = "InnerControlButton",
SubConrolProperty = "Command",
Data = new RelayCommand(CommandMethod),
},
new TargetControlData
{
SubConrolProperty = "Text",
SubControlName = "InnerContentTextBlock",
Data = "Hello"
},
new TargetControlData
{
SubConrolProperty = "Background",
SubControlName = "InnerContentTextBlock",
Data = Brushes.Green
},
new TargetControlData
{
SubConrolProperty = "Foreground",
SubControlName = "InnerContentTextBlock",
Data = Brushes.White
},
};
}
private void CommandMethod()
{
_staticCount ++;
var backgroundData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Background");
var textData = InnerData.FirstOrDefault(data => data.SubControlName == "InnerContentTextBlock" && data.SubConrolProperty == "Text");
if (backgroundData == null || textData == null) return;
var index = _staticCount%_list.Count;
backgroundData.Data = _list[index];
textData.Data = string.Format("{0} {1}", "Hello", backgroundData.Data);
}
public ObservableCollection<TargetControlData> InnerData { get; set; }}
7. TargetControlData code:
public class TargetControlData:BaseObservableObject
{
private string _subControlName;
private string _subConrolProperty;
private object _data;
public string SubControlName
{
get { return _subControlName; }
set
{
_subControlName = value;
OnPropertyChanged();
}
}
public string SubConrolProperty
{
get { return _subConrolProperty; }
set
{
_subConrolProperty = value;
OnPropertyChanged();
}
}
public object Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
}
Summary - you can pull control properties data from configuration file, or collect them by reflection.
regards,
The way you suggested - I don't think this would be possible.
But it can be done with normal properties, instead of dependency properties, something like:
UserControl xaml:
<StackPanel>
<TextBlock x:Name="tbOne"></TextBlock>
<TextBlock x:Name="tbTwo" Foreground="Red"></TextBlock>
</StackPanel>
UserControl code behind:
public string One
{
get
{
return this.tbOne.Text;
}
set
{
this.tbOne.Text = value;
}
}
public string Two
{
get
{
return this.tbTwo.Text;
}
set
{
this.tbTwo.Text = value;
}
}
and the usage of user control:
<local:UserControl1 One="test1" Two="test2"></local:UserControl1>
I have a RichTextBox in my MVVM program.
I would like to bind the RichTextBox.Selection property to my model.
To achieve this task, I've created a custom UserControl which contains a RichTextBox:
<UserControl x:Class="MyProject.Resources.Controls.CustomRichTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<RichTextBox x:Name="RichTextBox" SelectionChanged="RichTextBox_SelectionChanged"/>
</UserControl>
In my UserControl class:
// Selection property
public static readonly DependencyProperty TextSelectionProperty =
DependencyProperty.Register("TextSelection", typeof(TextSelection),
typeof(CustomRichTextBox));
[Browsable(true)]
[Category("TextSelection")]
[Description("TextSelection")]
[DefaultValue("null")]
public TextSelection TextSelection
{
get { return (TextSelection)GetValue(TextSelectionProperty); }
set { SetValue(TextSelectionProperty, value); }
}
The usage is:
<ResourcesControls:CustomRichTextBox TextSelection="{Binding ModelTextSelection}"/>
And I have this property on my model:
private TextSelection _TextSelection;
public TextSelection TextSelection
{
get { return _TextSelection; }
set { _TextSelection = value; }
}
I would like to get the RichTextBox.Selection property in my model, but TextSelection is always null.
I know I'm missing the binding between the RichTextBox.Selection property and his model but I don't know how to do it.
I think I'm missing something but I can't find what.
RichTextBox.Selection is not a DependancyProperty so you cannot bind to that.
But for your setup you just need to set the BindingMode as TwoWay on your UserControl (ssuming your model property name is ModelTextSelection)
<ResourcesControls:CustomRichTextBox TextSelection="{Binding ModelTextSelection, Mode=TwoWay}"/>
and in SelectionChanged method your need to update your TextSelection DependancyProperty with RichTextBox.Selection
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
TextSelection = richTextBox.Selection;
}
I have a User Control Library that I am loading dynamically. From that lib I am inserting a Tabitem into a TabControl. I can load the tab and show it without error. However, I can't seem to get the binding on the control working.
This is the code I use to load it and add it to the TabControl:
Assembly moduleAssembly = Assembly.Load("ControlLib");
UserControl uc = (UserControl)Application.LoadComponent(new System.Uri("/ControlLib;component/UserControl1.xaml", UriKind.RelativeOrAbsolute));
TabControl itemsTab = (TabControl)this.FindName("mainTabControl");
TabItem newTab = new TabItem();
newTab.Content = uc;
newTab.Header = "Test";
itemsTab.Items.Add(newTab);
itemsTab.SelectedItem = newTab;
This is the C# code for the control:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty TestStringProperty =
DependencyProperty.Register("TestString", typeof(string), typeof(UserControl1));
public string TestString { get; set; }
public UserControl1()
{
InitializeComponent();
TestString = "Hello World";
}
}
This is the XAML code for the control:
<UserControl x:Class="ControlLib.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Height="30" Width="100" HorizontalAlignment="Left" VerticalAlignment="Bottom" Text="{Binding Path=TestString, Mode=TwoWay}" />
</Grid>
</UserControl>
When the tab displays all I see if a blank in the TextBox rather than "Hello World"
What am I missing?
You would still be setting the DataContext of your user control to instance of the class. Just how you go about creating that instance differs as you would be loading that dll a runtime. But fundamentally the binding setup remains the same.
var assembly = Assembly.LoadFrom(#"yourdllname.dll");
Type type = assembly.GetType("ClassLibrary1.SampleViewModel");
object instanceOfMyType = Activator.CreateInstance(type);
DataContext = instanceOfMyType;
For how basic databinding works read MSDN documentation.
Make sure you select the correct framework on the top of the screen.
EDIT
Usually this is created as a separate class (ViewModel in MVVM pattern).
public partial class Window3 : Window, INotifyPropertyChanged
{
public Window3()
{
InitializeComponent();
DataContext = this;
TestString = "Hello World.";
}
string _testString;
///<summary>Gets or sets TestString.</summary>
public string TestString
{
get { return _testString; }
set { _testString = value; OnPropertyChanged("TestString"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
I am new to MVVM and databinding and I am having some trouble binding a gridview to a datatable dynamically. I am able to get the column headers to bind, but no data is being displayed in the grid itself.
My model simply returns a data table as the result of a SQL string passed to it.
My viewmodel just wraps the datatable and gets bound to the view.
Right now I am just trying to display the data by populating the gridview from the main window, but only the headers are being displayed.
I know there is data in the model.Results datatable though.
My viewmodel:
public class ResultsViewModel
{
private DataTable _dt;
public ResultsViewModel()
{
DataSource _ds = new DataSource();
_dt = _ds.Execute("select * from tbl_users");
}
public DataTable Results
{
get { return _dt; }
set { _dt = value; }
}
}
My code to populate the gridview from the mainwindow:
public MainWindow()
{
InitializeComponent();
ResultsView view = new ResultsView();
ResultsViewModel model = new ResultsViewModel();
GridView Grid = new GridView();
foreach (DataColumn col in model.Results.Columns)
{
Grid.Columns.Add(new GridViewColumn
{
Header = col.ColumnName,
DisplayMemberBinding = new Binding(col.ColumnName)
});
}
view._listView.View = Grid;
view.DataContext = model;
view.SetBinding(ListView.ItemsSourceProperty, new Binding());
_placeholder.Content = view;
}
The ResultsView xaml:
<UserControl x:Class="InDevReporting.Views.ResultsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ListView x:Name="_listView" />
</Grid>
Try setting your data context to model.Results.
ie change this line:
view.DataContext = model;
to this:
view.DataContext = model.Results;
Generally you would create a dependency property on your view model and specify the binding in the XAML. The grid should be clever enough to figure out what columns to draw:
<ListView ItemsSource="{Binding Results}" />
public MainWindow()
{
InitializeComponent();
// your code to instance and populate model
this.DataContext = model;
}
public class ResultsViewModel : DependencyObject
{
public static readonly DependencyProperty ResultsProperty = DependencyProperty.Register("Results", typeof(DataTable) , typeof(ResultsViewModel));
public DataTable Results
{
get { (DataTable)GetValue(ResultsProperty); }
set { SetValue(ResultsProperty, value); }
}
}
I've tapped this out from memory, so apologies if the code isn't exactly right. The easiest way to declare a new dependency property is to use the propdp code snippet. It's a lot of syntax to memorize.
I'm trying to create a user control with dependency properties to bind to. Internally I have a ComboBox that is bound to these same properties, but the binding only works one way. The ComboBox fills from the ItemsSource, but SelectedItem doesn't get updated back to the viewmodel I'm binding to.
A simplified example:
This is the view model to bind with the user control:
public class PeopleViewModel : INotifyPropertyChanged
{
public PeopleViewModel()
{
People = new List<string>( new [] {"John", "Alfred","Dave"});
SelectedPerson = People.FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _people;
public IEnumerable<string> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
}
private string _selectedPerson;
public string SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
}
}
This is the User control:
<UserControl x:Class="PeopleControlTest.PeopleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="56" d:DesignWidth="637">
<StackPanel >
<ComboBox Margin="11"
ItemsSource="{Binding BoundPeople, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding BoundSelectedPerson, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</StackPanel>
with code behind
public partial class PeopleControl : UserControl
{
public PeopleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty BoundPeopleProperty =
DependencyProperty.Register("BoundPeople", typeof(IEnumerable<string>), typeof(PeopleControl), new UIPropertyMetadata(null));
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new UIPropertyMetadata(""));
public IEnumerable<string> BoundPeople
{
get { return (IEnumerable<string>)GetValue(BoundPeopleProperty); }
set { SetValue(BoundPeopleProperty, value); }
}
public string BoundSelectedPerson
{
get { return (string)GetValue(BoundSelectedPersonProperty); }
set { SetValue(BoundSelectedPersonProperty, value); }
}
}
And this is how I bind the user control in the main window (with the windows data context set to an instance of the viewmodel)
<Window x:Class="PeopleControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:PeopleControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson}"/>
</Grid>
</Window>
The combobox in the user control fills with the names, but when I select a different name this doesn't get updated back to the view model. Any idea what I'm missing here?
Thanks!
Some properties bind two-way by default (Including SelectedItem) but your BoundSelectedPerson does not. You can set the Mode of the binding:
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson, Mode=TwoWay}"/>
Or you can make it TwoWay by default by setting a flag on the DependencyProperty:
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));