Submenu not displaying when bound to IsSubmenuOpen - c#

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.

Related

Updating ViewModel content in MVVM

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

Tabs Opening redundancy

Currently When I press on buttons they produce New pages and creates a new Tab at the Top. I'm trying to make a case where If the tab is already created it be redirected to the working one. May I get some tips or guidance please.
public void Show(string name)
{
IGridPort tab;
switch (name)
{
case "Contacts": tab = new ContactsGridViewModel(Events); break;
case "Businesses": tab = new ClientGridViewModel(Events); break;
default: tab = new QuickLaunchViewModel(Events); break;
}
Events.Publish(new ShowTabEvent(tab));
}
In my apps I typically have a base class called PageViewModel for each of the tabs on the main page and a derived class called DataPageViewModel for displaying pages that should only appear once (e.g. a record that's being edited). I maintain an observable collection of PageViewModel to pass as the ItemsSource into my tab control and I also maintain a dictionary of DataPageViewModel so I can look them up based on the data they are displaying:
public ObservableCollection<PageViewModel> Pages { get; private set; }
private Dictionary<string, DataPageViewModel> DataPages { get; set; }
The string that I use to key the dictionary is generally a combination of the page type and a unique identifier for the data being displayed. All that remains then is to check the dictionary before you create a page to see if another page displaying that data already exists, if it does then just set that page as the active one. Setting the active page can be done by getting the DefaultView of the ObservableCollection and calling MoveCurrentTo, but a better method in MVVM is to create a property in your model to hold the currently active page:
private PageViewModel _CurrentPage;
public PageViewModel CurrentPage
{
get { return _CurrentPage; }
set { _CurrentPage = value; RaisePropertyChanged(() => this.CurrentPage); }
}
Then just bind it to SelectedItem in your tab control:
<TabControl
ItemsSource="{Binding Pages}"
SelectedItem="{Binding CurrentPage, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" />
The binding is two-way so if the user selects a tab then CurrentPage will be updated accordingly, and if you set CurrentPage in your ViewModel then the corresponding tab will be selected in the View.

MvvmCross ICommand for elements inside ListView item [duplicate]

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

Using MVVM instead of main window for the following code

I'm using the following code which is copy pasted from the main window which was working as expected ,
I have created View which is user control and put the code of the
code from the main window XAML
In the View model I put reference for the User model
In the user control I put the code for from the the main window which
is related to the event handlers for example the
DropText_PreviewDragEnter & listbox_SelectionChanged
Currently I have 2 issues in the User Control which Im not sure how to overcome...
1. Errors in the user control for all the occurrence of the ListBox (for example from listbox_SelectionChanged ystem.Windows.Controls.ListBox.SelectedItems.Count > 0 . the Selected items are marked at red with the following error
"cannot access non-static property SelectedItems item source in static context". ,not sure what is the reason since in the main window it was the same as static.
2. Since I have copied the code from the main window there is references to user object in the user controlwhich I believe is not acceptable in MVVM ,how should I change it ? for example
var mySelectedItem = System.Windows.Controls.ListBox.SelectedItem as User;
or
bool remove = _UsersList.Remove((User) System.Windows.Controls.ListBox.SelectedItem);
Here is the code.
I will appreciate your help !
The view model
public partial class ModelView : UserControl
{
private const string DRAG_SOURCE = "DragSource";
public ModelView()
{
InitializeComponent();
DataContext = new ModelView();
}
//Drag Over from text box to List box
private void ListBox_PreviewDrop(object sender, DragEventArgs e)
{
object dragSource = e.Data.GetData(DRAG_SOURCE);
if (dragSource != null && dragSource is TextBox)
{
(dragSource as TextBox).Text = String.Empty;
}
if (!String.IsNullOrEmpty(e.Data.GetData(DataFormats.StringFormat).ToString()) && dragSource is TextBox)
{
_UsersList.Add(new User {Name = e.Data.GetData(DataFormats.StringFormat).ToString()});
}
else
{
e.Handled = true;
}
}
}
}
The Xaml is
<TextBox x:Name="name1"
AcceptsReturn="True"
AllowDrop="True"
PreviewDragEnter="DropText_PreviewDragEnter"
PreviewDrop="DropText_PreviewDrop"
PreviewMouseDown="DropText_PreviewMouseDown"
HorizontalAlignment="Left" Height="20" Margin="360,70,0,0" TextWrapping="Wrap" Text=""
VerticalAlignment="Top" Width="70"/>
....
The model view
internal class ModelView
{
private ObservableCollection<User> _UsersList = new ObservableCollection<User>();
public ObservableCollection<User> UserList
{
get { return _UsersList; }
}
public void InitUsers()
{
_UsersList.Add(new User {Name = "fff"});
//Sort the User collection
ICollectionView usersView = CollectionViewSource.GetDefaultView(_UsersList);
usersView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}
}
You already have two answers explaining why the first issue happend in the previous question. And follwoing points are what #Will said in comment as a mess in MVVM implementation that I can see in your codes :
UsersList in the model view is a Model as in Model-View-ViewModel.
And the model view it self is a ViewModel as in Model-View-ViewModel
Then what you call view model is actually a View in Model-View-ViewModel point of view. It inherits UserControl and UserControl is a view, no difference from Window or Page, etc. They're all View. And even if we agree to call it view model, then it violated MVVM principle everywhere, because view model shouldn't have reference to View/UI control object.
Not directly answering your question, but I hope you get a better prespective on MVVM pattern.
#phil correctly noted that you can't access the ListBox like this:
System.Windows.Controls.ListBox
What he failed to mention is that you shouldn't access a ListBox at all if you're using MVVM. Clearly you're not using MVVM now, but if you want to, then I would recommend that you read up on it so that you can get the full benefit from it. Just having a view and a view model does not mean that you're using MVVM.
In MVVM, we manipulate data, not UI controls. Therefore, you need to create a SelectedItem property in your view model and bind that to the ListBox.SelectedItem property and then you'll always have access to the item that is selected:
public User SelectedItem { get; set; } // Implement INotifyPropertyChanged here
...
<ListBox ItemsSource="{Binding YourCollection}" SelectedItem="{Binding SelectedItem}"/>
Now you can do something with the selected item like this:
string selectedItemName = SelectedItem.Name;
you have to access your listbox by
yourListBoxName.SelectedItems.Count > 0
you can't access it by
System.Windows.Controls.ListBox.SelectedItems.Count
same for
var mySelectedItem = System.Windows.Controls.ListBox.SelectedItem as User;
use the following instead
var mySelectedItem = yourListBoxName.SelectedItem as User;

WPF DataBinding ObservableCollection<T> to DataGrid

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)

Categories