I have a ListView that looks like this, that controls which tab in my application that is opened.
<ListView Grid.ColumnSpan="2" Grid.Row="1" SelectedItem="{Binding Path=SelectedSubstanceName}" Name="listView" ItemsSource="{Binding Path=Substances}" HorizontalAlignment="Stretch" Margin="2" VerticalAlignment="Stretch">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Lägg till" Command="{Binding AddSubstanceCommand}"/>
<MenuItem Header="Ta bort" Command="{Binding RemoveSubstanceCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I use the SelectedSubstanceName property to detect which tab to open, or switch to, if it's already open.
The property looks like this:
private SubstanceName selectedSubstanceName;
public SubstanceName SelectedSubstanceName
{
get
{
return selectedSubstanceName;
}
set
{
selectedSubstanceName = value;
OnPropertyChanged("SelectedSubstanceName");
if (selectedSubstanceName != null)
{
if (!Tabs.Any(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel))))
AddTab(selectedSubstanceName);
else
SelectedTab = Tabs.First(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel)));
}
}
}
The case I'm not able to cover is when the user clicks "someSubstance", the corresponding tab is opened, the user closes it, and "someSubstance" is still selected. If the user wants to open it again, he has to select some other substance (which will then be opened), and then click "someSubstance" again. Is it possible to trigger the property even when clicking the same ListViewItem?
I know I could add an event on double-click, but ideally, I want to avoid both events and double-clicks.
I think the problem is that after clicking an item the first time the list's SelectedItem gets set. After clicking the same item the second time SelectedItem won't change because it is already set to that item. What you should do is set the SelectedItem to null after handling the click.
Try to unselect all Items in your ListView after the tab is closed.
YOURLISTVIEW.UnselectAll();
So the next time someone selects an Item there will be a change.
You don't actually want to use the ListView class, but instead simply use the ItemsControl, since it is the most basic way of representing a sequence of elements, but without the extras such as SelectedItem, SelectedValue, etc. that any class deriving from Selector has.
From there, it's merely a matter of how to represent each item in the ItemsControl. The behavior you want is to know when a specific item has been clicked on, which would make the Button class a good candidate, since it handles click behavior through an ICommand interface. Obviously, since you know about DataTemplates and styling in general, you should already know that you can customize how the button looks (visually) without sacrificing the actual behavior (click-handling).
<ItemsControl ItemsSource="{Binding Path=Substances}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SomeStyleToChangeItsLook}"
Command="{Binding Path=SelectSubstanceCommand}"
CommandParameter="{Binding}"
Content="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public ICommand SelectSubstanceCommand { get; private set; }
private void SelectSubstance(object parameter)
{
// Add the substance that was "clicked" on here however you want to do it.
}
Keep in mind I don't know what framework you are using, so I just gave a general example of how the Command code might look in your view-model. The key to MVVM and using WPFs awesome UI is to always think of what behavior you want and which controls offer that behavior. Ignore how they actually look because that can be changed without losing that behavior.
Related
I'm trying to extend the app from a WPF MVVM tutorial as an exercise. I've found no solution on the net for this specific problem I'm facing here.
I have a ViewModel with an ObservableCollection called "StudentsToAdd". This collection is bound to an ItemsControl. Outside the ItemsControl I have a Button with a binding to the "AddCommand" command in the ViewModel. The relevant extract form my XAML looks as follows:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<Button Content="+" Command="{Binding AddToAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="3,0,3,0" Margin="50,0,0,0"/>
<Button Content="-" Command="{Binding RemoveFromAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5,0,5,0" Margin="5,0,0,0"/>
</StackPanel>
<ItemsControl x:Name="AddList" ItemsSource="{Binding Path=StudentsToAdd}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The + and - buttons will add or remove students from the StudentsToAdd collection. The "AddCommand" moves all entries from StudentsToAdd to another collection called "Students" when executed.
Now what I can't get to work is this: whenever a Student in StudentsToAdd is modified (after any keystroke: UpdateSourceTrigger=PropertyChanged). I want the Add Button to evaluate the CanExecute of AddCommand in the ViewModel so its IsEnabled property is automatically set accordingly. The command methods in the ViewModel currently look as follows:
private void OnAdd()
{
foreach (Student s in StudentsToAdd)
{
Students.Add(s);
}
StudentsToAdd.Clear();
StudentsToAdd.Add(new Student { FirstName = string.Empty, LastName = string.Empty });
}
private bool CanAdd()
{
if (StudentsToAdd != null && StudentsToAdd.Count > 0)
{
return StudentsToAdd.All(x => !string.IsNullOrWhiteSpace(x.FirstName) && !string.IsNullOrWhiteSpace(x.LastName));
}
return false;
}
Does anybody know how I can achieve this without coupling parts of the MVVM?
Does anybody know how I can achieve this without coupling parts of the MVVM?
There's not much context in your question. But, it seems that you are asking how to accomplish this entirely within the view model/model layer, without involving the view layer. At least, that's what you should be asking.
If so, it should be relatively simple, assuming the code you didn't show is written reasonably. That is, since your CanAdd() method depends on property values of the Student objects, you'll need to subscribe to the Student.PropertyChanged event, and raise the ICommand.CanExecuteChanged event any time any of the Student objects' PropertyChanged event is raised.
For what it's worth, I would also encapsulate the "can be added" logic in the Student class, rather than the ViewModel class. Expose that state as a single property that the ViewModel class can check. This will address a couple of things:
Your Student class seems like the more logical place to put code that determines whether the class is ready to be added to a list of Students, and
The ViewModel class can check to make sure it's that property that is changing, so it doesn't bother to go to all the work to check all the other Student objects every time any Student property changes, and each Student object will effectively be caching the "can be added" value, so that that work to check all the other Student objects is a simple property retrieval, instead of having to re-evaluate the state every single time.
I assume you already understand how to raise the ICommand.CanExecuteChanged event, but if not, here are a couple of posts that should help you with that:
CanExecuteChanged event of ICommand
ICommand CanExecuteChanged not updating
(You'll see that there are two basic strategies: implement something in the ICommand object that will explicitly raise the ICommand.CanExecuteChanged event, or call InputManager.InvalidateRequerySuggested() to force the Input Manager to call all the CanExecute() methods it knows about. IMHO, the latter is a pretty heavy-weight and less-desirable approach, hence my suggestion to use the ICommand.CanExecuteChanged event.)
I have an ObservableCollectiong<StringWrapper> (StringWrapper per this post) named Paragraphs bound to an ItemsControl whose ItemTemplate is just a TextBox bound to StringWrapper.Text.
XAML
<ItemsControl Name="icParagraphs" Grid.Column="1" Grid.Row="7" ItemsSource="{Binding Path=Paragraphs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<StackPanel Orientation="Vertical">
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Name="tbParagraph" TextWrapping="Wrap" AcceptsReturn="False" Text="{Binding Path=Text}" Grid.Column="0" KeyUp="tbParagraph_KeyUp" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#
public class StringWrapper
{
public string Text { get; set; }
public StringWrapper()
{
Text = string.Empty;
}
public StringWrapper(string text)
{
Text = text;
}
}
I'm trying to make it so when I press enter in a TextBox, I insert a StringWrapper in my ObservableCollection after the StringWrapper bound to the TextBox that's currently focused, which generates a new TextBox. So far, my code does this, though there are a couple glitches to work out.
My question is, how do I then set the focus to the newly generated TextBox? As far as I can tell, the control generation happens after the function that inserts the string returns.
I looked for something like an ItemsControl.ItemsSourceChanged event, but, at least, that name doesn't exist. I also tried attaching a handler to ObservableCollection.CollectionChanged, but that too seemed to fire before the TextBox was generated. Last, since the ItemsControl.Template is a StackPanel, I looked for a StackPanel.ControlAdded event, but couldn't find that either.
Ideas? Thanks!
You may have to handle CollectionChanged and then schedule the focus action to occur in the future using Dispatcher.BeginInvoke with a priority of Loaded. That should give the ItemsControl an opportunity to generate a container and perform layout.
So I'm developing a Windows Phone 8 app with the Caliburn.Micro framework. I'm trying to create a grid where I, at runtime add/remove elements such as TextBlock's at runtime. I've tried a few things to bind my code to the x:Name but nothing has worked so far.
So one of the things i tried was having a placeholder grid in my xaml aka View:
<Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
</Grid>
And then i my ViewModel i use the following to bind my ContentPanel Grid:
private Grid contentPanel;
public Grid ContentPanel
{
get
{
return contentPanel;
}
set
{
contentPanel = value;
NotifyOfPropertyChange(() => ContentPanel);
}
}
I then created a TextBlock to add to the grid:
TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);
And finally i added the TextBlock to my Grid:
ContentPanel.Children.Add(txt1);
When i run this code ContentPanel turn out to be equals null, why is that? Shouldn't Caliburn auto bind ContentPanel x:Name="ContentPanel" with the property ContentPanel?
I would appreciate your help in this matter.
My core problem, that i need solved is this:
I got a login page in my app where i show some pictures and text loaded from a server. As you can see below this is done with Image and a TextBlock When that server is offline or the wi-fi simply aren't enabled i want to replace this picture+text with a static image. Aka i want to remove the TextBlock from the StackPanel.
The part where i load and show the stuff form my server works great and looks like this in my xaml:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
So when the server is offline/wifi disabled i want to replace that with. so that the TextBlock is no longer there:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Is this even possible? If not what would the best semi-solution be?
EDIT 1: I've managed to setup the flow following the instructions from the accepted answer. But my BooleanToVisibilityConverter is not called, though my NotifyOfPropertyChange(() => IsConnectionAvailable); is getting called.
My Property:
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
How i change the bool: This code is called in my constructor for my ViewModel(just as a test to see if it was working):
IsConnectionAvailable = false;
TextBlock (without trigger code cause its the same as previous):
<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
It's like the Binding IsConnectionAvailable isn't working because i can change the name IsConnectionAvailable in my Xaml to anything and my NotifyOfPropertyChange(() => IsConnectionAvailable); will still be called.
Any ideas?
I can't even do a normal bind Visibility="{Binding Path=IsVisibil,Mode=TwoWay} to a public Visibility IsVisibil property. I've done this in other classes, but even this won't work??
EDIT 2: The problem that course the binding not to work, seems to lie somewhere in this code:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Solution to EDIT 1 and 2: I created an x:Name"Root" at the top of my xaml structure. Then changed the binding to:
ElementName=Root, Path=DataContext.IsVisibil
This is needed because the binding to visibility that I'm trying to set is inside another DataContxt.
This isn't the correct way to use CM, there are a number of areas where you are confusing the model and viewmodel and the binding functionality in CM.
What you are doing currently
You are attempting to have the CM framework look for a property called ContentPanel on your ViewModel and automatically figure out what properties on Grid to bind it to...
This won't work because of a few reasons:
I don't think there is a convention for Grid in CM - it's not really bindable in an obvious way (it's a layout container)
Grid is not a data enabled control - it doesn't know how to consume a collection and display dynamic rows out the box (it's a layout container)
What you are doing doesn't really make any sense (you have an instance of a grid in your UserControl and you have also instantiated a grid in your ViewModel - these are two separate instances of a control - you can't 'bind' them together - that's not how it all works)
CM and Bindings
When you using element name bindings e.g. x:Name with CM, it attempts to find a property on the ViewModel which matches the element name. At this point, depending on the conventions setup for the source control in question, CM will attempt to automagically wire up all the bits and pieces.
There are default conventions contained in ConventionManager which determine which properties to bind when you use element name bindings - e.g. for TextBlock, the Text property on the TextBlock is bound to the target property on the ViewModel.
http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - look at the class constructor on ConventionManager to see the out of the box conventions - there isn't one for Grid
Once a target property is found, CM will bind it up.
(As an aside: it's worth noting that if the control type is a ContentControl CM will do some composition magic so you can have viewmodels that contain other viewmodels and have a composition all bound up at runtime - great for screens which have multiple sub-windows etc)
The problem you have is that there is no convention setup for Grid out of the box - this is most likely because a Grid in SL/WPF is primarily used for layout, and is not really a 'data container' or data aware in any way (apart from the few dependency properties you can bind to) - i.e. I don't think it's possible to bind to a grid and get a dynamic number of columns/rows without some customisation to the control, hence the omission of any conventions
(think about it - if you are binding a grid to a collection, what should the grid do... add rows or columns? It can't really be supported in a sensible way)
Now bringing it back to SL/WPF for a sec:
Usually if you want a variable list of items you will need to bind to the ItemsSource property of a control which inherits from ItemsControl (or ItemsControl itself).
Many controls do this: if they need to display a dynamic number of items they will usually inherit from ItemsControl.
How does this tie in with CM?
Caliburn Micro knows how to bind up ItemsControl out of the box. This means you can have a property on your ViewModel containing a collection of items and after binding you get a dynamic view of these at runtime
For example - a CM bound ItemsControl might look like this:
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeTextualProperty}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now you just need a collection of objects to bind this to - each item in the collection becomes a new item in the control with its DataContext pointing to the bound item. I've made the assumption that you would want each item to be a ViewModel which contained the property SomeTextualProperty - I've defined that here...
// Provides a viewmodel for a textual item
public class TextItemViewModel
{
public string SomeTextualProperty { get; set;}
}
The VM that should contain the list of items would need to have a collection to bind against.
(Note: Since you are adding items to it at runtime you need to tell the UI when the collection changes - ObservableCollection gives you this for free as it implements collection changed notification events)
// This is the viewmodel that contains the list of text items
public class ScreenViewModel
{
public ObservableCollection<TextItemViewModel> TextItems { get; set; }
}
What else I would consider the incorrect approach
Your ViewModels shouldn't know about your View implementation i.e. they shouldn't reference any type of controls unless absolutely necessary (I can't think of a time when I had to put a control in a VM). ViewModels should model the view - but they shouldn't really need to know any specifics about what that view contains - this way they are more easily testable and they are easily reused
If you follow the above approach, you can get away with providing an application which re-uses the set of viewmodels, but provides different views for each. You can try this by replacing ItemsControl with another type of control in the view (as long as it's data aware such as a datagrid) and the VM will still work - the VM is view agnostic.
Your use of Grid in your VM is not ideal because Grid is a visual control, it is not data. Remember that the visuals are your View and the ViewModel should just contain data and events which notify the view of things happening
If I was doing this - the code would look more like the code I posted above.
To sum up
Model the information you wanted to show in a ViewModel (TextItemViewModel)
Add a collection of these objects to the main ViewModel (ScreenViewModel) using a change aware collection such as ObservableCollection
Add/remove items from the collection using the standard add/remove
Bind the ItemsControl in the view using x:Name bindings to the collection on your ScreenViewModel
Adding/removing items in the VM will fire property changed notifications. ItemsControl will watch for these events and update itself accordingly
Addendum
You could get away with just using an ObservableCollection<string> instead of a TextBlockViewModel but it's not clear if you want to add more properties to the items you are binding to the grid (such as IsHeading property for headings which you could then make bold/italic in the view)
If you want to just use strings just modify the DataTemplate to bind directly to the DataContext rather than a property on the DataContext
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
Ok in your case it's quite simple - your ViewModel should simply model the state of the server:
public class LoginPageViewModel
{
public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
}
Then bind the visibility of the textblock to this using a converter:
<TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
You will need to declare the static resource for the converter somewhere (in the control itself or your main resources dictionary for example)
It looks like there is a converter already defined in System.Windows.Controls somewhere, but in case you can't find it the implementation is pretty simple (you could probably do this a bit better to guard against invalid input but for brevity I've kept it tiny):
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also want to change the state from available/unavailable during the views lifecycle, so in that case you probably want to use the property changed events built in to PropertyChangedBase (which Screen also inherits) to let the view know when the property changes
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
Addendum 2
I prefer the terse CM syntax instead of being explicit when binding action messages - so your XAML would change from:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
To
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
(actually that might not be right with the $dataContext.Link part ... but then again it might be... see here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation)
I'm new to WPF and using MVVM. I have a view in which I want to display different content according to what a user selects on a menu. One of those things is another user control Temp which has a view model (TempVM) so I am doing this:
<ContentControl Content="{Binding Path=TempVM}"/>
and TempVM (of type TempViewModel)is null until the user clicks a button. Its data template is this
<DataTemplate DataType="{x:Type vm:TempViewModel}">
<view:Temp />
</DataTemplate>
That's fine, but the other thing I want to do is show a listbox when a user clicks a different menu item. So I am trying to do
<ContentControl Content="{Binding Path=Missions}"/>
(Missions is an observable collection of MissionData) and trying to template it like this:
<DataTemplate DataType="{x:Type ObservableCollection(MissionData)}">
<StackPanel>
<ListBox ItemsSource="{Binding}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go"/>
</StackPanel>
</DataTemplate>
But the compiler doesn't like the type reference. If I try doing it by giving the template a key and specifying that key in the ContentControl it works but obviously I see the ListBox and button when there's no Missions. Obviously I could make a user control and viewmodel and follow the same pattern as I did for the TempVM but it seems over the top. Am I going the right way about this and what do I need to do?
From what i see is that you try to use a Collection as a dataobject which is in my opinion bad practice. Having a DataTemplate for a collection is also problematic, like you already have witnessed. I would advice you to use a ViewModel for your missions collection.
class MissionsSelectionViewModel
{
public ObservableCollection<Mission> Misssions;
public MissionData SelectedMission;
public ICommand MissionSelected;
}
and modify your datatemplate to
<DataTemplate DataType="{x:Type MissionsSelectionViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Missions}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go" Command="{Binding MissionSelected}/>
</StackPanel>
</DataTemplate>
If I were to follow your pattern of implicit templates, I would derive a custom non-generic collection MissionDataCollection from ObservableCollection<MissionData> and use it to keep MissionData items. Then I would simply reference that collection in DataType. This solution gives other advantages like events aggregation over the collection that are useful.
However, it seems to me that the best solution is the following.
Add a IsMissionsListVisible property to your VM.
Bind the Visibility property of the ContentControl showing the list to the IsMissionsListVisible property.
Use a keyed DataTemplate resource.
Implement the logic that determines if IsMissionsListVisible. Supposedly it should be true when there is at least one mission in the selected item. But the logic may be more complex.
I would do it this way. In fact, I do it this way usually, and it gives several benefits. The most important is that I can explicitly control the logic of content visibility in various situations (e.g. async content refresh).
This is really peculiar. I tried a simple data binding example program. I tried to bind a collection (IList) to a list box. When i alter the collection, the list box is updated only if i maximize the window. Here are the snippets,
<ListBox x:Name="myBirthdaysListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<UniformGrid>
<Label Content="{Binding Name}"></Label>
<Label Content="{Binding DateOfBirth}"></Label>
</UniformGrid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public Window1()
{
InitializeComponent();
myCalendar = new List<Calendar>();
myBirthdaysListBox.DataContext = myCalendar;
}
I am just a beginner in wpf. Kindly let me know, if i have done some thing terribly wrong in here.
Try using a BindingList<Calendar> rather than just a List<Calendar>, As binding list raises events for when items get added/removed/etc.
What you're seeing is the control is redrawing when you resize, and it's going through all the data again.
Off the top of my head, I believe you need to implement INotifyPropertyChanged on your Calendar such that the binding list is notified if an item in it changes