Is there a way to reuse a WPF View using it with Caliburn.Micro?
In example I've got a DetailViewBase.xaml with a SaveAndClose and Cancel button. Now I want to use the DetailViewModelBase and the XAML in PersonView(Model).
I hope this is question is clear.
If my assumption is correct, I think what you mean is that you want to 'compose' a more complex view from several simpler viewmodels. You can inherit from viewmodels, but obviously a viewmodel can only have one active view at a time.
However, using bindings you can compose multiple viewmodels and have them all render their respective views together to make a more complex UI
e.g.
A VM with save/cancel buttons
public class ActionsViewModel
{
public void Save()
{
}
public void Cancel()
{
}
}
The corresponding view:
<UserControl>
<StackPanel Orientation="Horizontal">
<Button x:Name="Save">Save</Button>
<Button x:Name="Cancel">Cancel</Button>
</StackPanel>
</UserControl>
Another view which composes itself and the ActionsViewModel
public class ComposedViewModel
{
public ActionsViewModel ActionsView
{
get; set;
}
public ComposedViewModel()
{
ActionsView = new ActionsViewModel();
}
public void DoSomething()
{
}
}
The View:
<UserControl>
<StackPanel Orientation="Horizontal">
<TextBlock>Hello World</TextBlock>
<Button x:Name="DoSomething">Do Something</Button>
<ContentControl x:Name="ActionsView" />
</StackPanel>
</UserControl>
When you use the convention binding (or use the attached properties) to bind a ContentControl CM will automatically bind the VM to the DataContext and wire up the view. This way you can compose multiple VMs into a single more complex piece of functionality.
Additionally, because of the action message bubbling - if you were to remove the 'Ok' and 'Cancel' implementation on the ActionsViewModel and put them in the ComposedViewModel implementation, the action message will bubble up to the ComposedViewModel instance and fire the methods on there instead
e.g.
public class ActionsViewModel
{
// Remove the command handlers
}
public class ComposedViewModel
{
public ActionsViewModel ActionsView
{
get; set;
}
public ComposedViewModel()
{
ActionsView = new ActionsViewModel();
}
public void DoSomething()
{
}
// CM will bubble the messages up so that these methods handle them when the user clicks on the buttons in the ActionsView
public void Save()
{
}
public void Cancel()
{
}
}
EDIT:
Sorry I forgot about this - convention based bindings won't allow messages to bubble, but you can just use the Message.Attach attached property for this to work:
// Make sure you include the caliburn namespace:
<UserControl xmlns:cal="http://www.caliburnproject.org">
<StackPanel Orientation="Horizontal">
<Button x:Name="Save" cal:Message.Attach="Save">Save</Button>
<Button x:Name="Cancel" cal:Message.Attach="Cancel">Cancel</Button>
</StackPanel>
</UserControl>
You can bind the ViewModel to the View explicitly using code like this:
cal:Bind.Model={Binding DetailViewModelBase}
Related
I have a ListView that is bound in the XAML to an AsyncCommand defined in the ViewModel. All examples are cut down for brevity.
XAML
<listView ItemSource="{Binding Path=ReportCommand.Execute.Result}" />
<Button Command="{Binding ReportCommand}">Click</Button>
MVVM
In the constructor:
ReportCommand = new AsyncCommand<List<MyPoco>>(() => LoadReport());
In the class:
public IAsyncCommand ReportCommand { get; private set; }
private async Task<List<MyPoco>> LoadReport()
{
return await _service.GetListOfPocos();
}
(fyi - this is based on Stephen Cleary's MSDN article)
okay, so far, all well and good.
However, on another control on the Window, the user does something that requires this ListView to be cleared (for example, they log out). I know how I can get my various ViewModels to talk to each other behind the scenes, but how do I get to clear this "command" so that the List that is bound to by ListView is empty?
Thanks
Griff
Could you not store the result of the async command in another property in your viewmodel, and bind your ListView to that property? That would give you complete control over how and when the content changes. Something like this:
public ViewModelConstructor()
{
ReportCommand = new AsyncCommand(() => LoadReport());
}
public IAsyncCommand ReportCommand { get; private set; }
private async Task LoadReport()
{
MyPocoList = await _service.GetListOfPocos();
}
public List<MyPoco> MyPocoList
{
get { ... }
set { ...; RaisePropertyChanged("MyPocoList"); }
}
and your XAML would change to:
<ListView ItemSource="{Binding MyPocoList}" />
<Button Command="{Binding ReportCommand}">Click</Button>
If you needed to clear the listbox, you can simply set MyPocoList = null;
I'm assuming you're implementing INotifyPropertyChanged in your ViewModel with this example.
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 am trying to learn the Prism Navigation support. Presently, I have a prism Region and I want to load view to that region using RegionManager.RequestNavigate(). The navigation does occur, however the IsNavigationTarget() of INavigationAware is not invoked, even if the ViewModel of the Navigation Target view implements INavigationAware interface. Here is the code that I am using.
Shell:
<StackPanel Margin="10">
<TextBlock Text="Main Window"/>
<Button Content="RegionA" Command="{Binding NavigateToACommand}" />
<ContentControl prism:RegionManager.RegionName="MainRegion"/>
</StackPanel>
ShellViewModel:
private void NavigateToA () {
Uri uri = new Uri("RegionAView", UriKind.Relative);
RegionManager.RequestNavigate("MainRegion", uri);
}
RegionAView:
<UserControl x:Class="NavigationExample.RegionAView"
<Grid>
<TextBlock Text="This is Region A"/>
</Grid>
</UserControl>
RegionAViewModel
public class RegionAViewModel : INavigationAware{
public RegionAViewModel() {
}
public bool IsNavigationTarget(NavigationContext navigationContext) {
return false; //Not Invoked
}
public void OnNavigatedTo(NavigationContext navigationContext) {
//Gets Invoked
}
}
RegionAView.xaml.cs
[Export("RegionAView")]
public partial class RegionAView : UserControl {
public RegionAView() {
InitializeComponent();
}
}
Why does the IsNavigationTarget() not getting invoked prior to completion of Navigation?
I think your problem is that you export your view as singleton. modify VM and V as follow:
[Export("RegionAView")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class RegionAView : UserControl
{
public RegionAView()
{
InitializeComponent();
}
}
Basically, IsNavigationTarget will be invoked when you have existing instances. But it will not work for newly created instance.
I have an objects array of my custom type. And I have a button array related with these objects. I want to change any object by clicking on his button. So buttons must "know" about their objects. And objects must "know" about their buttons. How to bind them each other (one object binds with only one button and vice versa)?
In addition, it must be in C# code, not XAML, because I'll construct these arrays dinamically.
Example. There are Map object with Cell objects in it (VM means view model):
public class MapCellVM
{
public bool IsPassable { get; set; }
}
public class MapVM
{
public MapCellVM[,] CellMap { get; set; }
}
Each cell must be presented as a button. So the user can change IsPassable property of any cell object just by clicking on particular button.
I'm not sure how you realized your issue so far, so I can just give you an example how I would have designed it. As you want to show a list of custom objects I would use an ItemsControl to show them and a command on each custom object which is triggered from XAML. That would still give you the chance to handle everything in code. Here is a sample:
Custom object
public class CustomObject
{
public ICommand DoSomethingCommand { get; private set; }
public CustomObject()
{
DoSomethingCommand = new DelegateCommand(DoSomething);
}
private void DoSomething(object obj)
{
// TODO: do something
}
}
If you're not already using an implementation of DelegateCommand, have a look at the source of it in Prism (or anywhere else, there are a lot of samples).
MainWindow (part of the XAML)
<ItemsControl x:Name="itemsControl" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="Do Something" Command="{Binding DoSomethingCommand}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
MainWindow (Code behind)
public partial class MainWindow : Window
{
private ObservableCollection<CustomObject> _customObjects;
public MainWindow()
{
InitializeComponent();
_customObjects = new ObservableCollection<CustomObject>();
Loaded += MainWindowLoaded;
}
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
itemsControl.ItemsSource = _customObjects;
_customObjects.Add(new CustomObject());
}
}
That's it. Every time you click the button the method DoSomething is executed.
Assuming the following view model definition:
public class MyObject {
public string Name { get; set; }
}
public interface IMyViewModel {
ICommand MyCommand { get; }
IList<MyObject> MyList { get; }
}
And a UserControl with the following code behind:
public class MyView : UserControl {
public IMyViewModel Model { get; }
}
If my XAML looked like this:
<UserControl>
<ListBox ItemsSource="{Binding MyList}">
<ListBox.ItemTemplate>
<TextBlock Text="{Binding Name}" />
<Button Content="Execute My Command" cmd:Click.Command="{Binding Path=MyCommand, ?????????}" cmd:Click.CommandParameter="{Binding}" />
</ListBox.ItemTemplate>
</ListBox>
How can I bind my Button to the ICommand property of my code-behind class?
I'm using Prism and SL 3.0 and I need to bind each button in my list box to the same command on my view model.
Before my UserControl had a specific name and I was able to use the ElementName binding, but now my UserControl is used multiple times in the same parent view so I can't use that technique anymore and I can't figure out how to do this in XAML.
If it is my only option I can do it manually in the code-behind, but I'd rather do it declaratively in the XAML, if possible.
You need a DataContextProxy for this to work because you're no longer in the context of the UserControl. You've moved out of that and there is no good way to reach back into that context without something like the DataContextProxy. I've used it for my projects and it works great.