A ComboBox and a Button are on different user controls and do not see each other. Both of them are bound to the same ViewModel though.
The Button has a Click event which executes a method in the code-behind of its user control.
I want to be able to execute this method when I change ComboBox. For example let's say the SelectedItem of the ComboBox is bound to a property in ViewModel, now when it is changed, I want the method in the other control to be executed.
You mentioned that these two controls are unaware of one another, but are bound to the same ViewModel. I've found the easiest way to execute some type of code upon a property changing is to execute it directly from the set { } access modifier. Consider the following:
public class MyViewModel
{
private MyObject _currentObject;
public MyObject CurrentObject
{
get { return this._currentObject; }
set
{
this._currentObject = value;
//Usually where OnPropertyChanged goes or however you implement INotifyPropertyChanged
//Where we call our command, logic can be introduced if needed.
//Also we may just call the method directly.
this.SomeCommand.Execute(null);
}
}
private Command _someCommand;
public Command SomeCommand
{
get
{
return this._someCommand ?? (this._someCommand = new Command(
() =>
{
this.SomeMethod();
},
() =>
{
//The CanExecute test, returns bool.
return this._currentObject != null ? true : false;
}));
}
}
private string SomeMethod()
{
return "I just got called yo!";
}
}
Update:
Since the code to execute exists in the handler you will have to do a little work to get this right. Even though this violates MVVM principals it will work:
You will need to move the code to execute to the ViewModel underneath a new method if not already done, let's stick with the SomeMethod() name.
You need to obtain a reference to the ViewModel in the handler. I'm assuming this is already set as the DataContext
MyViewModel viewModel = DataContext as MyViewModel;
Next you will need to call that method from the handler:
viewModel.SomeMethod();
Now you may call the method from the set { } portion of your property contained within the ViewModel.
I hope this works for you.
you would want to create an object (i.e. SelectedComboBoxItem) that raises property changed. then use the generated event to start the event trigger you are trying to create.
Related
I'm working on a MVVM WPF application where i bind several values from ViewModel to View.
Now i have created a new ViewModel where i have to bind a value to a TextBox after a Button click. When i tried this simple binding it didn't work for me. To my surprise the binding works when the value is assigned in the constructor.
I'm confused.
ViewModel:
public ABCViewModel{
txtItems = "Hello world"; //this works
}
private string m_stxtItem = "";
public string txtItems
{
get { return this.m_stxtItem; }
set
{
if (this.m_stxtItem != value)
{
this.m_stxtItem = value;
}
}
}
public ICommand BindTextValue { get { return new RelayCommand(SeriesBinding); } }
private void SeriesBinding()
{
txtItems = "Hi"; //does not work
}
XAML:
<TextBox Text="{Binding txtItems,Source={StaticResource ABCViewModel}}" />
<Button Command="{Binding BindTextValue,Source={StaticResource ABCViewModel}}">Click</Button>
Why this didn't work and where am i wrong?
Simply answer: you are missing the INotifyPropertyChanged-implementation required for automatic data binding.
Extended answer to why it works when setting the value in the constructor:
the initial binding (reading of the value) from the view happens AFTER your ViewModel-constructor was called and your value was set
You need to implement the INotifyPropertyChanged interface on your ViewModel.
Here's some MSDN links on what the interface is and how to implement it:
INotifyPropertyChanged Interface
How to: Implement the INotifyPropertyChanged Interface
Basically because you've set the ViewModel as the DataSource for the View, upon construction, the View will look to the ViewModel for its values. From this point onwards the ViewModel needs some mechanism for notifying the View of changes. The View is not periodically updated or anything like that. This is where the INotifyPropertyChanged interface steps in. The WPF framework looks out for these events being fired and makes the View refresh its value.
I'm writing an application to read and analyze some logs my company software uses. There are multiple types of logs, but let's take only two for the purpose of describing my problem. Namely, logs of TypeA and TypeB. I have designed one class to hold a single line of log data, named LogLine which looks like below.
public class LogLine
{
public long LineNum { get; set; }
public string Msg { get; set; }
}
So here's my problem/requirement.
In my main ViewModel, I'd like to read logs of each type only once when the application loads. Read TypeA logs one time, and store in an ObservableCollection of LogLine instances, do the same for TypeB. Then depending on my choice the DataGrid displays logs from one type, and if I click a button at any time, the same DataGrid should display logs from the other type. Note that my logs data doesn't change, I simply want to display my choice of logs.
For this I created three classes, namely, ControllerMain, ControllerA, and ControllerB. The last two derive from the former like so:
public class ControllerMain
{
public ControllerMain()
{
LogLineList = new ObservableCollection<LogLine>();
}
private ObservableCollection<LogLine> logLineList;
public ObservableCollection<LogLine> LogLineList
{
get { return logLineList; }
set { logLineList = value; }
}
}
public class ControllerA : ControllerMain
{
public ControllerA() { }
// More stuff here
}
public class ControllerB : ControllerMain
{
public ControllerB() { }
// More stuff here
}
As you can guess ControllerA is intended to hold logs of TypeA, and associated properties and methods unique to those logs. Same goes for TypeB logs.
In my ViewModel, I have instances of each of the classes above like so, and at application load I read log data and store in appropriate class object.
public ControllerMain COMMON_LOG { get; set; }
public ControllerA A_LOG { get; set; }
public ControllerB B_LOG { get; set; }
public ViewModelMain()
{
isAType = true;
ClickCommand = new CustomCommand(ClickCmd, CanClickCmd);
A_LOG = new ControllerA
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetAData()
};
B_LOG = new ControllerB
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetBData()
};
// This simulates switching to already loaded logs.
// When I do this the log lines don't change, but I want to refresh the datagrid and display correct info.
LoadAppropriateLog();
}
private void LoadAppropriateLog()
{
if (isAType)
{
COMMON_LOG = A_LOG;
isAType = false;
}
else
{
COMMON_LOG = B_LOG;
isAType = true;
}
}
My View binds to the COMMON_LOG instance like below:
<DataGrid Grid.Row="0" Margin="5"
Name="dgLogs"
AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding COMMON_LOG.LogLineList}">
Then at the click of a button, I call the above LoadAppropriateLog() method, so it will simply assign the instance of appropriate type to COMMON_LOG which is the instance I've used to data bind.
The problem is that when I do so, since the actual data in each instance's LogLineList doesn't change, the DataGrid doesn't automatically update to reflect my choice of logs.
Is there a way to manually refresh the DataGrid from my ViewModel after every time I switch the type of log?
If you'd like to run the project and see, here's a download link:
Download the VS Project
If you're binding to a property of a class in XAML, either
The property should never change its value after a binding would first see it, and should usually be readonly just to avoid mishaps -- or
The class should implement INotifyPropertyChanged and the property should raise PropertyChanged in its setter.
In your case, you're changing the value of COMMON_LOG, and you're never changing the value of its LogLineList.
tl;dr: So your main viewmodel needs to implement INotifyPropertyChanged, and raise PropertyChanged in the setter for COMMON_LOG. Anything that doesn't do those things isn't a viewmodel.
LogLineList being an ObservableCollection won't accomplish anything: What that class does is raise notifications when items are added, removed or replaced. That doesn't happen at any time after the binding sees it. Those instances of ObservableCollection don't even know that the main viewmodel even exists, so they certainly can't be expected to raise notification events when its properties change. Nor should they: Everybody is responsible for exactly his own notifications.
In fact, if you've made a design decision that those collections never change after initialization, use ReadOnlyCollection instead of ObservableCollection. Creating one is easy: Call List<T>.AsReadOnly<T>(). For any IEnumerable<T>, just call e.ToList().AsReadOnly(). ObservableCollection signals "you can add stuff to this". But nobody should. So don't give them ideas.
My program is composed of a TreeView and two contentPresenters at ground level. The mainWindow, TreeView, and each contentPresenter all have their own viewModels.
I would like to call a function in the mainWindowViewModel from the TreeViewViewModel.
I need to do this because the mainWindowViewModel controls what is displayed in the contentPresenters, and I would like to manually update the display.
I'm guessing I would do something like this...
TreeViewViewModel:
public class TreeViewViewModel
{
//Do I need to declare the MainWindowVM?
public TreeViewViewModel() { ... }
private void function()
{
//Command that affects display
//Manually call function in MainWindowVM to refresh View
}
}
I have tried to get access to the MainWindowVM from the TreeViewViewModel by using:
public MainWindowViewModel ViewModel { get { return DataContext as MainWindowViewModel; } }
But it doesn't make much sense. because the MWVM is not the DataContext of the TreeViewViewModel.
The delegate method used in this and the linked answer can be used in any parent-child relationship and in either direction. That includes from a child view model to a parent view model, a Window code behind to the code behind of a child Window, or even pure data relationships without any UI involved. You can find out more about using delegate objects from the Delegates (C# Programming Guide) page on MSDN.
I just answered a similar question to this earlier today. If you take a look at the Passing parameters between viewmodels post, you'll see that the answer involves using delegate objects. You can simply replace these delegates (from the answer) with your method(s) and it will work in the same way.
Please let me know if you have any questions.
UPDATE >>>
Yes, sorry I completely forgot you wanted to call methods instead... I've been working on too many posts tonight. So still using the example from the other post, just call your method in the ParameterViewModel_OnParameterChange handler:
public void ParameterViewModel_OnParameterChange(string parameter)
{
// Call your method here
}
Think of the delegate as being your path back to the parent view model... it's like raising an event called ReadyForYouToCallMethodNow. In fact, you don't even need to have an input parameter. You could define your delegate like this:
public delegate void ReadyForUpdate();
public ReadyForUpdate OnReadyForUpdate { get; set; }
Then in the parent view model (after attaching the handler like in the other example):
public void ChildViewModel_OnReadyForUpdate()
{
// Call your method here
UpdateDisplay();
}
As you have multiple child view models, you could define the delegate in another class that they both have access to. Let me know if you have any more questions.
UPDATE 2 >>>
After reading your last comment again, I've just thought of a much simpler method that might achieve what you want... at least, if I understand you correctly. It is possible for you to Bind directly from your child views to your parent view model. For instance, this would allow you to Bind a Button.Command property in a child view to an ICommand property in your parent view model:
In TreeViewView:
<Button Content="Click Me" Command="{Binding DataContext.ParentCommand,
RelativeSource={RelativeSource AncestorType={x:Type MainWindow}}}" />
This of course assumes that an instance of the parent view model in question is set as the DataContext of the MainWindow.
The easiest way is to pass a method as Action to the constructor of the child view model.
public class ParentViewModel
{
ChildViewModel childViewModel;
public ParentViewModel()
{
childViewModel = new ChildViewModel(ActionMethod);
}
private void ActionMethod()
{
Console.WriteLine("Parent method executed");
}
}
public class ChildViewModel
{
private readonly Action parentAction;
public ChildViewModel(Action parentAction)
{
this.parentAction = parentAction;
CallParentAction();
}
public void CallParentAction()
{
parentAction.Invoke();
}
}
I have a problem with two way binding a TextBox content to a property in another class. Searching stackoverflow gave a lot of tips/solutions but none seem to work.
In my XAML code I have:
< TextBox ... Width="336" IsReadOnly="True"
Text="{Binding Path=AssignedClearProgram, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
(... I removed all non important items)
In the accompanies cs code I have:
public CombiWindow(Combi combi)
{
ViewModel = new CombiViewModel(combi);
DataContext = ViewModel;
}
In the CombiViewModel:
[UsedImplicitly]
public string AssignedClearProgram { get; set; }
It seems that the first time I assign AssignedClearProgram, the textbox is filled with the text that I set, however after the window is displayed and AssignedClearProgram gets updated from the code (i.e. the set method is called), the data is not updated in the screen.
Does anybody have a solution to update the textbox when this variable is changed?
Kind regards,
Michel
Your viewmodel class needs to implement INotifyPropertyChanged and you need to raise that interface's event whenever you change the property. Then the binding will spot changes and update the textbox.
Your view model class should implement the INotifyPropertyChanged interface.
Your property would then look like the following:
private string assignedClearProgram;
public string AssignedClearProgram
{
get { return assignedClearProgram; }
set
{
if (assignedClearProgram != value)
{
assignedClearProgram = value;
// Notify property has changed here using PropertyChanged event from INotifyPropertyChanged.
}
}
}
Read this article for an example of how to implement the INotifyPropertyChanged interface and utilize its PropertyChanged event.
With my colleague we are having a problem with SilverLight regarding the NotificationObject of Prism.
Our problem is;
We bind an event handler to one of our DependencyProperties
Then we call RaiseChangedEvent in the setter
When we debug, we see that eventhandler is assigned and it is called when the proerty value is changed.
But when we chage the property value once agian from in the UI, this time we see that our event handler is not called.
So we want to be sure:
If event handler is still binded (unless it is not null, we believe it is binded)
If it is binded why the event is cancelled or why our anonymous event handler method is not called again (the breakpoint is not hit)
You can see the code snippets below.
My question is:
Is there a way to see why the RaisePropertyChanged("MyProperty"); is not calling an eventhandler assigne to this DependencyProperty called Requestor? Any suggestions?
If I generalize: Is it possible to step into RaisePropertyChanged("Requestor"); call so that what it does and where it exits?
See my code snippets:
// My dependency property in my ViewModel
public CompanyEntity MyProperty
{
get { return _MyProperty; }
set
{
_MyProperty = value;
RaisePropertyChanged("MyProperty");
}
}
// And my Dependency Property Event handler setting in my user control:
public static readonly DependencyProperty FactoryProperty =
DependencyProperty.Register("Factory", typeof (FactoryEntity), typeof (FactoryPicker),
new PropertyMetadata((x, y) =>
{
// Some settings and processes
}));
Just a guess: don't you have to coerce as explained in http://forums.silverlight.net/forums/p/57516/146193.aspx?
Check if you have missed the Mode=TwoWay declaration at binding.