I have a list of items bound to a MvxBindableListView with a MvxItemTemplate.
I usually have 4 items in my list bound to my view. Data gets updated and the view displays the new data just fine.
Now, I want to add two buttons to this item template. However, relative source binding is not available with MvvmCross. (see image)
But I'm having difficulties working out a solution to this.
I have tried the ItemClick binding of the list item, but that only gives me 1 possibility of click and I need 2.
Can anyone help?
See the second option in the answer in MVVMCross changing ViewModel within a MvxBindableListView - this covers one way to do this.
Using that approach you'd expose a list of objects like:
public class Wrapped
{
public ICommand GoThruCommand { get; set; }
public ICommand OpenCommand { get; set; }
public string Name { get; set; }
}
and you'd use an axml list template with bound controls like:
<TextView
...
local:MvxBind="{'Text':{'Path':'Name'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'GoCommand'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'ThruCommand'}}" />
if you've got suggestions/requests for relative source in mvx, please add them to https://github.com/slodge/MvvmCross/issues/35
Related
I have my SoapBox.Document 'Register'
[Export(SoapBox.Core.ExtensionPoints.Workbench.Documents, typeof(IDocument))]
[Export(CompositionPoints.Workbench.Documents.Register, typeof(Register))]
[Document(Name = DOC_NAME)]
class Register : AbstractDocument
{
public Receipt actualReceipt;
private const string DOC_NAME = "Register";
public Register()
{
Name = DOC_NAME;
Title = "Recipe Document Title";
SomeProperty = "Hello from the recipe document!";
}
}
In this Document I want to user UserControls witch are kind of a own "View"
Like a ListView for all ReceiptPositions
So now I got my Model Receipt and ReceiptPosition
Model Receipt
class Receipt
{
public int Id { get; set; }
public string Receiptnumber { get; set; }
public IList<ReceiptPositions> ReceiptPositions { get; set; }
and Model ReceiptPosition
class ReceiptPosition
{
public int Id { get; set; }
//public Receipt Receipt { get; set; } using for Database
public int Position { get; set; }
public string Article { get; set; }
}
So now I want to add a UserControl witch displays a List of all articles in ReceiptPositions.
But how do I bind the data so that when a new ReceiptPosition gets added to the IList in Receipt the UserControl get 'refreshed' automatically?
Here is a visual example of whatI need..
Host with Data and two PLugins wich each show the same Data but in a different way.
You can use an ItemsControl for this purpose.
xaml:
<ItemsControl ItemsSource="{Binding MyReceipt.ReceiptPositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Where you put your view -->
<TextBox Text="{Binding Article}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Can be whatever Panel type you want -->
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
cs:
private Receipt _myReceipt;
public Receipt MyReceipt { get { return _myReceipt; } set { _myReceipt = value; OnPropertyChanged("MyReceipt"); } }
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyReceipt = new Receipt { ReceiptPositions = new ObservableCollection<ReceiptPosition>() };
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Foo" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Bar" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Baz" });
MyReceipt.ReceiptPositions[0].Article = "Frabazamataz";
}
Explanation:
The ItemsControl allows you to bind a list to its ItemsSource Property to use as the DataContext to each view created by the DataTemplate.
The Observable Collection gives PropertyChange notifications automatically with each item added, removed, or changed.
This allows you to have a very flexible list of items based solely on your data.
Death's answer is correct, i.e. you use DataTemplates. If your views and data templates are in a MEF plugin then you need to import both the plugins and the data templates that map the view models to the views. In the other question you posted about this it was obvious that you're trying to export your plugin user controls...personally I think this is a bit misguided. If your main application is using MVVM then your plugins should as well. In this case your plugins should export an IPlugin class and also specify a DataTemplate that maps it to a view. As I indicated on the other page, the data template must be imported as well so that you can add it to the global resources.
I've created a project that shows this in action using the classes you provided in your uother question, you can download it here. The main points to look at are the data templates in the plugin project and the two places where things are imported in the main project.
Note that in my demo I'm requiring each plugin to explicitly specify a DataTemplate for its view and view model, but you may not want to do this so I've also added a chunk of commented-out code at the bottom of App.xaml.cs that shows how to avoid that (to make it work I had to add the view type to the IPlugData class, but that's only needed for this one example). If you choose to create the DataTemplates manually then the plugins don't need to specify the data templates and they also don't need the custom ResourceDictionary that holds them.
If you have an questions feel free to post back here in the comments.
I'm trying to show a SubMenu of items. Every time IsSubmenuOpen is set to true, I make a call in my view model to refresh the list that the MenuItem is bound to.
Unfortunately, this doesn't work illustrated here (even though Windows has 3 subitems):
View Model:
public ObservableCollection<Item> CustomLayoutList { get; set; }
public bool LayoutOptionsOpen { set { UpdateCustomLayoutList(); } }
private void UpdateCustomLayoutList()
{
CustomLayoutList.Clear();
var items = GetItems();
foreach(var item in items)
{
CustomLayoutList.Add(item);
}
}
Binding in Xaml:
<MenuItem ItemsSource="{Binding CustomLayoutList }" IsSubmenuOpen="{Binding LayoutOptionsOpen, Mode=OneWayToSource}"></MenuItem>
EDIT: Apparently on my computer this works fine. The list populates correctly and shows the SubMenu. For some other people, however, the SubMenu wouldn't open - despite verifying that the list has items in it.
To resolve this, I changed my solution to listen to the SubMenuOpen event, and in my xaml.cs code-behind I call UpdateCustomLayoutList directly. This works, but why did my initial solution not work, and is there a better way to solve this? Ultimately I would like to have zero code-behind.
I have a list on my XAML page bind to my ViewModel. The list Show only the entries - there is no Feature to edit or update them (they are read from Server api).
In the application bar I have a button for reloading the list (sending again the request to the Server).
What must I do for this "reload Feature"?
I think about following:
removing the existing collection of my entries
firering the LoadData again
Are there any snippets for my question?
What is about Memory issues because of my previous existing collection?
Something like this would work if you think your callback will be pretty light. If you think it may be heavy with a lot of items coming back then this may not be the most efficient way but would still work:
public class YourViewModel
{
public ObservableCollection<YourDataType> YourCollection { get; set; }
public ICommand ReloadDataCommand { get; set; }
public YourViewModel()
{
YourCollection = new ObservableCollection<YourDataType>();
ReloadDataCommand = new DelegateCommand(ReloadData);
}
private void ReloadData()
{
//Get your new data;
YourCollection = new ObservableCollection(someService.GetData());
RaisePropertyChange("YourCollection");
//Depending on how many items your bringing in will depend on whether its a good idea to recreate the whole collection like this. If its too big then you may be better off removing/adding these items as needed.
}
}
In XAML:
<Button Content="Reload" Command="{Binding ReloadDataCommand}" />
<List ItemsSource="{Binding YourCollection}">
<!-- All your other list stuff -->
</List>
Hope this helps
I am programming a Windows 8.1 App using C#/XAML as well as the MVVM-Light Toolkit.
In my program there is a Schedule that consists of 3 components:
a GridView with 5 elements for Monday, Tuesday, ....
a ListView with x elements each showing the start- and end-time of
the current period. x depends on the number of period the user chose
for his schedule to have.
a GridView with 5*x elements that represent the places for the events
set by the user.
These 3 components are again inside a FlipView to enable multiple Schedules.
I enabled this in code via the following objects:
public class Schedule
{
public int WeekNumber { get; set; }
public ScheduleComponentSettings ScheduleComponentSettings { get; set; }
public ScheduleComponents ScheduleComponents { get; set; }
}
public class ScheduleComponents
{
public ObservableCollection<WeekDay> WeekDayItems { get; set; }
public ObservableCollection<FreePeriod> FreePeriodItems { get; set; }
public ObservableCollection<PeriodTime> PeriodTimesItems { get; set; }
public ObservableCollection<LessonTime> LessonTimesItems { get; set; }
}
In my ViewModel I have an ObservableCollection of the Schedule class:
public ObservableCollection<Schedule> ScheduleComponentsList
{
get
{
return _ScheduleComponentsList;
}
set
{
if (_ScheduleComponentsList == value)
{
return;
}
RaisePropertyChanging(ScheduleWeekListPropertyName);
_ScheduleComponentsList = value;
RaisePropertyChanged(ScheduleWeekListPropertyName);
}
}
The FlipView and its elements bind to that as follows (this is ofc. shortened to show only the ItemsSources):
<FlipView
ItemsSource="{Binding Main.ScheduleComponentsList, Source={StaticResource Locator}}"
<FlipView.ItemTemplate>
<DataTemplate>
<GridView
ItemsSource="{Binding ScheduleComponents.WeekDayItems}"/>
<ListView
ItemsSource="{Binding ScheduleComponents.PeriodTimesItems}"/>
<GridView
ItemsSource="{Binding ScheduleComponents.FreePeriodItems}"/>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Now here is the problem:
When I change a property on an element inside the FreePeriodItems or even when I replace the collection completely, the View only updates when I reload the entire Page. Same for all the other properties I update in one on the ScheduleComponents.
This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
Now I am sitting on the problem for ages now.
This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
That's exactly how ObservableCollection<T> works. It raises events only when the list itself changes, when you add or remove elements:
Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
from ObservableCollection Class
To make it work implement INotifyPropertyChanged on your WeekDay, FreePeriod, PeriodTime and LessonTime. When it's done you'll get event not only when collection changes but also when any of the items that already are part of collection is modified.
I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)