I'm using Galasoft's Light MVVM for my Siverlight project.
I have setup everything as instructed: the ViewModel is bound to View's DataContext;
I have a canvas named inkCanvas in the View.
When the ViewModel gets the updated project data, I need to reference inkCanvas to create a CanvasRender instance public CanvasRender(Canvas canvas, ProjectData pdata).
The problem is in MVVM, the ViewModel knows nothing about View, so how can I reference a control (inkCanvas) in View?
P.S. (Edited): The workaround I made is: when I pass the project data to the ViewModel, I also pass the inkCanvas from View's code-behind. hmmm, now my code-behind is not clean.
Per the comments above, one way to do this is to extend Canvas and keep the reference to CanvasRender inside that class.
public class MyCanvas : Canvas
{
private CanvasRender _canvasRender;
private ProjectData _data;
public ProjectData Data
{
get { return _data; }
set
{
_data = value;
_canvasRender = new CanvasRender(this, _data);
}
}
public MyCanvas() : base()
{
}
}
You'd probably want to also make ProjectData a Dependency Property so that it's bindable.
This allows you to maintain the MVVM pattern, because now you can write in XAML:
<local:MyCanvas ProjectData="{Binding ViewModel.ProjectData}" />
In MVVM Pattern, you won't reference a Control directly in ViewModel. In MVVM, all is "binding". You inkCanvas will be binding to a property in your ViewModel.
public class MyViewModel : INotifyPropertyChanged
{
private readonly StrokeCollection _mystrokes;
public MyViewModel ()
{
_mystrokes= new StrokeCollection();
(_mystrokesas INotifyCollectionChanged).CollectionChanged += delegate
{
//the strokes have changed
};
}
public event PropertyChangedEventHandler PropertyChanged;
public StrokeCollection MyStrokes
{
get
{
return _mystrokes;
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And XAML:
<InkCanvas Strokes="{Binding MyStrokes}"/>
Edit :
Maybe the workaround for your case is to use EventToCommand : this allow tobind an UI event to an ICommand directly in XAML ( and use Args to pass a ref to the inkCancas)
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=LoadedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
If your going to use the EventToCommand approach (which you tried in another answer), then instead of using the PassEventArgsToCommand property use the CommandParameter property and bind it to your Canvas.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding Path=CanvasLoadedCommand}"
CommandParameter="{Binding ElementName=inkCanvas}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Then in your ViewModel:
public class ViewModel
{
private Canvas m_canvas;
public RelayCommand<Canvas> CanvasLoadedCommand { get; private set; }
public ViewModel()
{
CanvasLoadedCommand = new RelayCommand<Canvas>(canvas =>
{
m_canvas = canvas;
});
}
}
So as soon as your canvas is loaded, you should then have a reference to it saved in your view model.
Related
How to bind RichTextBox to a flag (true/false value). For example i want the flag to be true if the text in the RTB is edited. And also the binding should be two-way.
You have to use two things to solve your problem.
You have to add System.Windows.Interactivity reference and use this link in your xaml:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Here is an xaml (view part) example:
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
...
<RichTextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding TextChangedCommand}"
x:Name="textChangedCommand" />
</i:EventTrigger>
</i:Interaction.Triggers>
</RichTextBox>
...
After this, you have to use an ICommand implementation in your ViewModel:
Here is a simple example to use in "ViewModel" part:
public partial class MainWindow : Window
{
RelayCommand _textChangedCommand;
public RelayCommand TextChangedCommand
{
get
{
if (_textChangedCommand == null)
_textChangedCommand = new RelayCommand(() => IsEdited = true);
return _textChangedCommand;
}
}
private bool _isEdited;
public bool IsEdited
{
get
{
return _isEdited;
}
set
{
_isEdited = value;
}
}
public MainWindow()
{
InitializeComponent();
//Use the following code if you want to use xaml.cs as ViewModel but It is not recommended, it is not correct MVVM pattern only simple example.
DataContext = this;
}
}
There are several way to implement the ICommand interface.
Here you can find one with description.
I have a CheckBox that's set up like so:
<CheckBox x:Name="ViewTypeCheckbox" IsChecked="{Binding ViewType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Refresh}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
This functions as it's supposed to. When checked or unchecked by mouse click, the command is fired in the ViewModel.
You see the checkBox is databound to a bool property ("ViewType") that regularly turns from true to false and viseversa in response to user input.
The problem is I need the EventTrigger to fire when checked or unchecked by the ViewModel.
I've tried changing the "EventName" to "Checked", "IsChecked" and "UnChecked" but that doesn't seem to do anything.
Is there any additional code I need to implement? How would I get this to work?
You don't need Interaction.Triggers,
WPF provides support for commands out of the box.
Try simplifying your XAML by using a command attribute instead. It should resolve your issue.
If you need to call some code when the ViewTypeCheckbox.IsChecked changes its value, you can simply register an event handler to the PropertyChanged event of your view model (assuming it implements INotifyPropertyChanged), then call the code when the property ViewType changes:
myViewModel.PropertyChanged += new PropertyChangedEventHandler(onPropertyChanged);
...
private void onPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName=="ViewType")
{
// Do your stuff here
// Ex. fire the Refresh Command
}
}
This can be done in the ViewModel class itself or whatever you need.
To complete the example I add a short implementation of INotifyPropertyChanged in the viewmodel class:
public class MyViewModel : INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler!=null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
...
// property definition to bind to ViewTypeCheckbox.IsChecked
private bool _viewType;
public bool ViewType
{
get { return _viewType; }
set
{
if (value != _viewType)
{
_viewType = value;
RaisePropertyChanged("ViewType");
}
}
}
}
Hope it helps
You don't need a trigger to execute your action. You can do it in the viewmodel when the property change.
public Boolean ViewType
{
get
{
return this.something;
}
set
{
this.something = value;
if (true == this.something)
{
this.Refresh();
}
}
}
I have a xaml file named MyWindow.xaml, and this xaml has a checkbox declared as..
<CheckBox Name="chkView" IsChecked="{Binding Path=IsChkChecked, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Checked="chkView_Checked" Unchecked="chkView_Checked" />
In MyWindow.xaml.cs,
public partial class MyWindow: UserControl,INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
}
private bool isChkChecked;
public bool IsChkChecked
{
get { return isChkChecked; }
set
{
isChkChecked= value;
OnPropertyChanged("IsChkChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Now, Iam trying to access this property from another class and change the property, but the checkbox is not getting binded to bool property.
MyLib.MyWindow wnd;
wnd= (MyLib.MyWindow)theTabItem.Content;
wnd.IsChkChecked = true;
Any suggestions would be appreciated.
Your view doesn't bind to IsChkChecked as it doesn't live in the DataContext. Usually you would declare a ViewModel with the property and declare the DataContext to be an instance of this ViewModel. A quick fix would be to change the constructor of the view to set the DataContext to the View itself or change the Binding (as dkozl suggested):
public MyWindow()
{
InitializeComponent();
this.DataContext = this;
}
If you don't specify another binding source by default it will search in DataContext and I cannot see that you set it anywhere. One way is to set RelativeSource against binding to point to Window that publishes IsChkChecked property
<CheckBox Name="chkView" IsChecked="{Binding Path=IsChkChecked, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
Is there a way to call methods from the view from the view model? Is this good practice to do so? If not, how would I hide elements in the view from the view model? I'm just a bit confused because I'm used to working with ASP.Net, with code behind, etc.
xaml.cs
btnsave.visibility = visibility.hidden;
btnclose.visibility = visibility.hidden;
For your specific example of hiding elements in the view, you probably want to set up some properties in the ViewModel that define the conditions under which those elements are visible. Then you bind the Visibility property (with a BooleanToVisibilityConverter, most likely) of those elements in the View to those properties in the ViewModel.
More generally, you want to keep the direct coupling between them minimal if you can, but sometimes "reality" gets in the way. I've had some cases where I've passed in the View to the constructor of the ViewModel. Other cases where it's been an interface that the View implements and that gets passed into the ViewModel. So there are options. But you should make sure you HAVE to go that route before doing it.
Example:
XAML:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="_B2VC" />
</Window.Resources>
<StackPanel>
<Button Content="Save" Visibility="{Binding IsSaveButtonVisible}" />
<Button Content="Close" Visibility="{Binding IsCloseButtonVisible}" />
</StackPanel>
</Window>
ViewModel:
public class ViewModel: INotifyPropertyChanged
{
#region INPC Stuff
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private bool _IsSaveButtonVisible;
public bool IsSaveButtonVisible
{
get { return _IsSaveButtonVisible; }
set
{
if (_IsSaveButtonVisible != value)
{
_IsSaveButtonVisible = value;
RaisePropertyChanged("IsSaveButtonVisible");
}
}
}
private bool _IsCloseButtonVisible;
public bool IsCloseButtonVisible
{
get { return _IsCloseButtonVisible; }
set
{
if (_IsCloseButtonVisible != value)
{
_IsCloseButtonVisible = value;
RaisePropertyChanged("IsCloseButtonVisible");
}
}
}
}
Then your ViewModel changes those properties in response to whatever it needs to (say for instance Save is only valid if they've changed something - once that something is changed, the property on the ViewModel gets updated and bam, that gets propogated to the View.
If you need further examples, i'd just suggest going and reading on MVVM. It takes a bit to grok, but its awesome once in use.
I've totally lost in the command binding that is used in MVVM. How should I bind my object to the window and/or its command to the control to get method called on the Button Click?
Here is a CustomerViewModel class:
public class CustomerViewModel : ViewModelBase
{
RelayCommand _saveCommand;
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(param => this.Save(), param => this.CanSave);
NotifyPropertyChanged("SaveCommand");
}
return _saveCommand;
}
}
public void Save()
{
...
}
public bool CanSave { get { return true; } }
...
ViewModelBase implements the INotifyPropertyChanged interface
Here is how Button is bound to the command:
<Button Content="Save" Margin="3" Command="{Binding DataContext.Save}" />
An instance of the CustomerViewModel is assigned to the DataContext of the window that contains a Button.
The given example is not working: I've put break point into the Save method but execution doesn't pass to the method. I've saw a lot of examples (on the stackoverflow too), but can't figure out how binding should be specified.
Please advise, any help will be appreciated.
Thanks.
P.S. Probably I need to specify RelativeSource in the Button binding... something like this:
Command="{Binding Path=DataContext.Save, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
but which type should be specified for ancestor?
What you are trying to do is to bind directly to the Save method. This is not how to do it.
Assuming that you have set the DataContext of your View to an instance of CustomerViewModel, this is how you bind to the SaveCommand:
<Button Content="Save" Margin="3" Command="{Binding SaveCommand}" />
You do not have to call NotifyPropertyChanged("SaveCommand");.