Without breaking MVVM, is there a way to expose some properties of a child control in a user control so that the window or other user control that utilizes it can access these properties directly?
For instance I have a user control that has a listview set up with gridviewcolumns, headers, and is bound to a view model. But the list view in the user control has selected item properties and such that I'd like to expose to the host without having to do something like usercontrol.customListView.property. Or is that how I should do it? I'd like to go just usercontrol.property, omitting customListView. Perhap I should just create properties in the user controls code behind that return the list view controls properties that I want attached directly to the user control?
I feel like that latter option doesn't really break MVVM since they are exposed for the host to interact with, not really related to the view itself. Any suggested would be appreciated.
EDIT: In fact, I'd really like to have a SelectedItem property directly on the user control that is not ListViewItem or object, but actually of the datatype contained that doe like:
public MyDataType SelectedItem {
get {
return customListView.SelectedItem as MyDataType;
}
}
Would that be permissible in MVVM? Because I don't see how I could have that in the ViewModel, seems like it would have to be in the partial class code behind.
This is pretty common task when you want to put something repeated into UserControl. The simplest approach to do so is when you are not creating specialized ViewModel for that UserControl, but sort of making custom control (build with the use of UserControl for simplicity). End result may looks like this
<UserControl x:Class="SomeNamespace.SomeUserControl" ...>
...
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}" ...>
</UserControl>
.
public partial class SomeUserControl : UserControl
{
// simple dependency property to bind to
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(SomeUserControl), new PropertyMetadata());
// has some complicated logic
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SomeUserControl),
new PropertyMetadata((d, a) => ((SomeUserControl)d).ValueChanged()));
private void ValueChanged()
{
... // do something complicated here
// e.g. create complicated dynamic animation
}
...
}
Usage will looks like this in containing window
<l:SomeUserControl Text="Text" Value="{Binding SomeValue}" ... />
As you can see SomeValue is bound to Value and there is no MVVM violations.
Of course, you can create a proper ViewModel if view logic is complicated or required too much bindings and it's rather easier to allow ViewModels to communicate directly (via properties/methods).
Related
So I currently have a Window with a TabControl. The MainWindow has its own ViewModel and all the TabItems have their own ViewModels also.
I can easily change tabs from the MainWindow ViewModel through a bound SelectedIndex property. What I would like to do is change to another tab from code that runs within ANOTHER tab viewmodel. Since the Tabs are not part of the MainWindowViewModel, I am looking for a clean way to change the Tab without resorting to code behind to do it.
There are also cases, where I might need to change the tab from something such as a message prompt. I thinking my only way is to create and event and subscribe to that from MainWindowViewModel.
So I solved this with an EventAggregator.
public static class IntAggregator
{
public static void Transmit(int data)
{
if (OnDataTransmitted != null)
{
OnDataTransmitted(data);
}
}
public static Action<int> OnDataTransmitted;
}
First ViewModel sends data.
public class ModifyUsersViewModel : INotifyPropertyChanged
{
private void change_tab(int data)
{
IntAggregator.Transmit(data);
}
}
Second ViewModel receives data and then does something with it.
public class MainWindowViewModel : INotifyPropertyChanged
{
private int _Tab_SelectedIndex = 0;
public int Tab_SelectedIndex
{
get
{
return _Tab_SelectedIndex;
}
set
{
_Tab_SelectedIndex = value;
OnPropertyChanged(new PropertyChangedEventArgs("Tab_SelectedIndex"));
}
}
public MainWindowViewModel()
{
IntAggregator.OnDataTransmitted += OnDataReceived;
}
private void OnDataReceived(int data)
{
Tab_SelectedIndex = data;
}
}
Rather than trying to bind to SelectedIndex, if the TabItems have their own view models, then you can create a property for each of those view models: IsSelected and then bind the TabItem.IsSelected property to that:
<TabItem IsSelected="{Binding IsSelected}">
This prevents the view models from needing to know the index of their corresponding TabItem, something I would argue is a detail that should be specific to the view and something the view model should not concern itself with. What if you add another TabItem or want to change the order? Now you've got changes to make in the view models for something that could be just simple change to the view.
I have a user control with a dependency property:
public ObservableCollection<Exclusion> SelectedExclusions
{
get
{
return (ObservableCollection<Exclusion>)GetValue(SelectedExclusionsProperty);
}
set
{
SetValue(SelectedExclusionsProperty, value);
}
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions),
typeof(ObservableCollection<Exclusion>),
typeof(TimeSeriesChart),
new PropertyMetadata(default(ObservableCollection<Exclusion>)));
I am adding a selected exclusion to this collection on key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.ExclusionProviders[0].Exclusions[this.hitTestInfo.DataSeriesIndex]);
}
}
In the view model I have this property & backing variable:
private ObservableCollection<TimeSeriesLibraryInterop.Exclusion> selectedExclusionsToDelete = new ObservableCollection<TimeSeriesLibraryInterop.Exclusion>();
public ObservableCollection<TimeSeriesLibraryInterop.Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
this.RaisePropertyChanged();
}
}
Finally the binding in the view:
<userControl1 SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The dependency property collection is initialised and populated however the view model property setter is never hit when the dependency property collection changes (Add). I have no binding errors in the output window. Is there something I'm missing here?
Looks like you're adding an item to the collection rather than replacing the collection. You won't hit the vm collection property's setter that way.
If you want to your viewmodel to respond to items being added to the SelectedExclusionsToDelete collection, the viewmodel will need to handle the SelectedExclusionsToDelete.CollectionChanged event. "Properly" handling that event (remove, add, move, clear, etc.) is a real hassle, but if it's not a giant collection you can often get away with something quick and dirty: Treat any change as a whole new collection. I think that's exactly the case you've got, too.
Alternatively, for an even quicker and dirtier approach, I think you could make it a two-way binding by default and have the control assign a new ObservableCollection to this.SelectedExclusions in OnKeyDown. The binding will pass it back to the viewmodel and hit the setter.
I say proper because all the examples I have seen all seem to contradict each other and or fall short in some respects. I need to be able to bind a class object to my DP from XAML or set it programmatically in cb:
<local:MyControl MyDP="{Binding MyObject}"/>
or
MyControl mycontrol = new MyControl(){MyDP = MyObject};
and then within the control, two way bind elements to properties of the binding object:
<TextBox text="{Binding MyDP.text, ElementName=MyControl}"/>
I would think this is pretty standard stuff, but the lack of cohesion I have seen in people trying to write examples has led me to believe otherwise.
This is how I do it:
Assume a parent control that contains a status bar (User Control) it's markup looks like this:
<ContentControl x:Name="XFoot" Grid.Row="1" Grid.ColumnSpan="3" >
<UserControls:UCStatusBar />
</ContentControl>
Now in the UserControl named status bar it's dependency property looks like this:
public StatusUpdate CurrentStatus
{
get { return (StatusUpdate)GetValue(CurrentStatusProperty); }
set { SetValue(CurrentStatusProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentStatus. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentStatusProperty =
DependencyProperty.Register("CurrentStatus", typeof(StatusUpdate), typeof(UCStatusBar),
new PropertyMetadata(
new StatusUpdate() { ErrorMessage="", IsIndeterminate=false, Status="Ready"}
)
);
Status Update looks like this, it's just a container for three properties shown in the status bar.
public class StatusUpdate
{
public StatusUpdate()
{
Status = "";
ErrorMessage = "";
IsIndeterminate = true;
}
public string Status { get; set; }
public string ErrorMessage { get; set; }
public bool IsIndeterminate { get; set; }
}
}
Anyone can update the status bar by accessing the CurrentStatus property of the statusbar. NOTE to make a two way binding, that's done in XAML... within the binding directive, just press space bar and the property window will show binding modes. Pick two way.
PS: In Visual Studio, when you first create the DP just type in propdp and press the tab button, the entire DP structure is inserted for you automatically. As a result DPs are easy to implement.
How do DPs work two-way?
If you are using XAML binding you simply tell it via the MODE property that it's two way. This means that the GUI changes will update the properties when user changes the values.
<TextBox Text="{Binding Path=ThePropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Notice the Mode value and the UpdateSourceTrigger says, don't want for focus to change, update it right away.
Wait a minute, nothing is happening when I bind and make changes!
Three things are required for a binding to work 1) DataContext must be set either in code behind or as a static resource in the XAML 2) The pathname to the property name must exactly the CLR property name and 3) There must be content in the property.
Can I fire an Event from somewhere else to update the property?
Sure... the first step is to set up the static eventhandler in the UserControl like this:
public static EventHandler<string> NewData;
Then in the CTOR wire it up like this:
NewData+=OnNewData;
Then the event handler looks like this:
private void OnNewData(object sender, string data){
//setting this string property notifies WPF to update the GUI
ThePropertyName = data;
}
The other code does this...
MyUserControl.OnNewData(this, "Now is the time for all good men to...");
I've got a ListView which is bound to a list of objects. When I select an item in the ListView, I catch a SelectionChanged event and then pass the selected object off to a details view.
protected void list_selectionChanged(object sender, SelectionChangedEventArgs e) {
var myObject = theList.SelectedItem as MyObjectType;
detailsView.DataContext = myObject;
}
detailsView is a UserControl in the same WPF as the ListView. It contains some XAML like so:
<Label Content="{Binding Path=deviceId}"></Label>
<l:MyUc deviceId="{Binding Path=deviceId}" />
Inside MyUC, I've got a DependencyProperty defined:
public static readonly DependencyProperty deviceIdProperty = DependencyProperty.Register("deviceId", typeof(Guid), typeof(MyUC), new FrameworkPropertyMetadata(null));
public Guid deviceId {
get { return (Guid)GetValue(deviceIdProperty); }
set { SetValue(deviceIdProperty, value); }
}
The Label shows the deviceId, but the property inside MyUC never gets set.
Can anyone spot my mistake?
When you use a Dependency Property in XAML, the set method never gets called. If you want to "see" this set, you need to add a property changed callback, as the binding mechanism directly sets the dependency property without your setter.
For details on how to implement this, see the PropertyChanged callbacks section of MSDN.
First, it would be helpful if you could add the actual XAML code where you define the ListView and it's properties.
Second, you should look at the output console (in Visual Studio debug session of course) and see whether there are binding errors regarding the bindings you defined.
It is very probable that the bindings provide values that does not fit the deviceId dependency property type and thus it never changes.
I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.