I am currently running into an issue where I want all of my tabs to be immediately bound to my ViewModel. For some reason, it seems that WPF doesn't bind my other TabItem's until I select them for the first time. I am eager for a solution in this because I have validation on those others tabs. Until the binding occurs on those other tabs, my app thinks everything is valid when it isnt. If it helps, I am using FluentValidation for my validation.
I have tried using someone's TabControlEx to see if that would help me but it doesn't. I also tried to cycle through all the tabs after I load the data to force the binding but that didn't always work on slower devices. I am not a fan of this solution either.
I don't know why you are being voted down, seems like a valid question, but you will still get a vague answer, due to the nature of the TabControl:
The default style for WPF TabControl only contains a single ContentControl which is used for displaying all tabs. Thus, the visual tree representing a tabs content is created on demand; and tears down to be replaced with a new visual tree when the content/tab switches.
The common issue here, is that switching between tabs becomes slow, and thus there are a couple of solutions out there:
Binding to ItemsSource of TabControl in WPF
https://github.com/ButchersBoy/Dragablz
However, what happens with the solutions is that ContentPresenters are cached...but they still wont be created until a user first hits the tab. Thus, you will still have the same issue.
Thus you need some sort of architectural change. You need your set of view models to be validated from a trigger in code, on initial startup. Waiting for the tab control to render items just isn't going to work for you.
You probably need to bubble the error up in your view model hierarchy to display in the TabItem.Header or in the window itself; perhaps you might have a MainWindowViewModel where you can display a top level error.
Related
I'm creating a Windows 10 Universal app, there are two panels in my main page.
In tablet/desktop mode, both are visible at the same time (just two simple Grids, one fills the left side and the other one fills the right side)
However, in phone mode, there's a pivot and user can swipe between panels.
(I could use SplitView and hamburger menus which work easily in both Tablet/Desktop and Phone modes, but a pivot makes more sense in my case)
So, What I currently did is, I have two instances of each panel. PhonePanel1 and PhonePanel2 are two grids inside two pages of my pivot control, and TabletPanel1 and TabletPanel2 are two grids which can be shown side by side. and I use AdaptiveTriggers to detect page width and switch between these two view methods.
However, having two instances of each panel is not a good approach, because those are showing basically the same content.
Is there any better approach to do this? Maybe changing Grid's parent when triggers are changing view mode? or something better?
The only way to change the parent that I can think of is to write some code behind that will render them in the new location. A suitable solution may be to defer the loading of the controls until they are needed.
x:DeferLoadStrategy=”Lazy“
If you add this property to your controls that are duplicated, they will only be loaded when required. So on a phone the PC/Tablet view should never be loaded, unless the user is running in Continuum on an external display.
On a PC you may still end up with both loaded (If the user resizes the window) - On a PC there are usually more resources so it's less of a problem there in most cases.
I looked into the code behind approach to move the controls to a different parent and I think honestly it's premature to do that when it might not even improve performance.
I have gone for the Defer Load Strategy property and found performance to be fine, and it requires very little code. My duplicate controls are sharing the same ViewModel so I pretty much just added the property to ensure resources are only used when essential.
There is nothing wrong in your approach. To reduce memory usage, when adaptive trigger happens for example on the phone, set TabletPanel1 and TabletPanel2 visibility to collapsed.
If all panels use the same datacontext then leave it as is, if not set datacontext for collapsed controls to null.
You could create each of your panels as user controls then you've only need to define the contents once but each control could be included in each of the layouts you want.
Ok, this is going to be a 1000ft long question, but there's a lot to cover so here goes:
I am creating a paged items control, the purpose of which is to display very large collections in a paged format. I've created a repository on GitHub which can be found here. I have removed any styling for simplicity.
Upon starting the application, it looks like this:
This is pretty straightforward really, there's navigation buttons, an items per page selector but that isn't really important. The problem here is when you click the button "Open New Window".
This will open a new MainWindow, but on the first window, the collection disappears, as shown below:
The image above shows the old window in front, as you can see, there is no list of content as there is on the new window.
So, after smashing my head against a wall for a couple of hours, I am in need of assistance. I'll provide an overview of how the project is structured.
AnagramPagedItemsControl
The control being used for displaying the content is a custom control called AnagramPagedItemsControl, it is responsible for handling navigation between pages. I think the key property here is the PagedCollection.
The PagedCollection dependency property holds the collection which is bound to the Models property in the TestItemsViewModel.
TestItemsViewModel
This is the DataContext of the MainWindow, each window instance should create it's own view model. The CreateTestItems() method is responsible for creating the list of test items.
LazyPagedCollection
The purpose of this collection is to encapsulate the logic of a paged observable collection, it only loads pages when they are needed, hence the laziness.
It exposes methods like NextPage which are called in the AnagramPagedItemsControl when the user clicks on the various navigation buttons. The view model can also call navigation on the LazyPagedCollection, this allows the view model to call navigation without having to go through the view to do it.
TL;DR
When I create a new Window, the content of the previous window disappears. The problem is almost certainly with the control however I am stuck as to how to fix the problem.
This is quite a large problem to look at so I'd be very grateful for anyone who can look into it. Again, the source code is here, please feel free to suggest alternatives or pick out anything that I may have overlooked.
Had some time to spare, so:
The problem is the setter for the CollectionView property in the style for AnagramPagedItemsControl in generic.xaml.
This does not instantiate a new ListBox every time the style is applied; it will just create the one ListBox, the first time the style is created, and use that value over, and over again. So in effect, every instance of MainWindow shares the same ListBox.
You can see this by setting the Tag property of PART_CollectionView to (for instance) "1" in SetupBindings(ItemsControl PART_CollectionView). When you open a new window, you'll see that PART_CollectionView.Tag contains the same value you previously assigned.
EDIT: I needed to skip control creation during post back -- see my answer below.
I'm working on a very basic front end to a simple tool and I wanted to present some data in a much more sorted and useful way, instead of making one huge wall of text. I found this tutorial on building a simple tabbed interface using MultiView, but have run into a bizarre problem. I can't use Ajax tabs because of legal hissy fits over 3rd party software.
My webpage is a basic ASP.NET page with a user control plopped in the middle of it. In this control's ascx file, I defined the Menu (empty) and the MultiView (also empty) so that I can dynamically populate the tabs with content driven from an external file.
When the default page's OnInitComplete function is called, I call through to the user control to load the data file, then build out the tabs and the view content based on the loaded data. I tried doing this from PageLoad, PreInit, and CreateChildControls, but I kept getting an errors saying that I was setting the the MultiView's active view index at an invalid time (and also that there were 0 views despite the fact I just added a bunch of them):
ActiveViewIndex is being set to '0'. It must be smaller than the
current number of View controls '0'. For dynamically added views, make
sure they are added before or in Page_PreInit event.
But OnInitComplete appears to work just fine, so I went with that.
I iterate over the loaded data (multiple lists of strings), and for each list, I add a MenuItem with the list's title to the Menu and a View to the MultiView. The View is populated with a table->row->cell as in the above tutorial. In the cell, I add the list title and a CheckBoxList data bound to the list of strings.
So far so good, but when I click on a tab (or one of the checkboxes, etc) and there is a postback or something like that (the screen flashes as the site redraws itself), there is now a duplicate set of MenuItems immediately after the original. Each time I click on a tab or checkbox, another set of menu items are added.
I clear the MenuItem's Items list prior to building the controls and I verify that the controls hierarchy is structurally as expected after the control construction. Yet when one of my callbacks is called, my MenuItem list magically has some items added to it. None of my other controls appear affected at all. As a hack, I can remove the duplicates manually in my menu's OnMenuItemClick event, but I'd have to do the same in any of the callbacks I receive. Obviously I'd rather prevent this from happening. This has me stumped and I haven't been able to find anything online about it. Why would one set of controls have some content duplicated, yet every other control maintain its state correctly? My code is really simple so there isn't a way to add additional menu items without also adding the views. Anyway, there are a correct number of items prior to clicking on the tab/checkbox, an additional set immediately following in the callback.
This is my first time using ASP.NET, so I'm learning as I go. :) Thanks!
My problem was that I was not testing for postback before creating the controls. The code below is working for me.
In my user control's code behind:
protected void OnInitComplete( EventArgs e )
{
if( !Page.IsPostBack )
{
CreateMyControls();
}
}
I'm working with quite complex WPF windows with a lot of controls in it.
In my context, the user experience we chose to have is to first display the window, then load the data and databind (while displaying a progress bar).
With the help of WPF traces in Visual Studio, I have noticed that databinding is invoked twice before the window is shown :
when we call InitializeComponent() which parses the XAML.
when we call ShowDialog().
Because there is no data yet, it takes quite some time for the databinding mechanism to try fallback and default values.
In order to speed the display of the window to the user, is there a way to disable the WPF databinding until the window is shown ?
Thanks in advance.
EDIT 1 :
I have more that 400 controls on the form. The call to InitializeComponent() is taking 700ms to complete. I don't know how much of that is tied to databinding, may be I'm mistaken.
EDIT 2 :
According to dotTrace profiler, InitilizeComponent() WPF cost is 65.43% divided in :
34.25% System.Windows
22.57% System.Windows.Markup
8.29% Other
EDIT 3 :
We're down to 272ms for InitializeComponent().
We have a lot of converters, singletons are now used for all of them.
Definitely.
One straight option would be to remove your data-binding-related XAML markup and do the same thing from code-behind, but only after the window has been shown (or on any other condition you find suitable). See this MSDN example on how to databind in code.
Another option would be to not bind to your "real" data sources directly (it's unclear why that would cause a slowdown, but I 'm taking your word for it) but rather to proxy objects that have an on/off switch: start with the switch turned off, making the proxy return empty datasets, and turn it on after the window has been displayed.
Interesting point. Is binding time so large?
In InitializeComponent() DataContext usually is null and it should take milliseconds to resolve binding. The problem likely is in another place.
Using default values in DataBinding mechanism is clear, fast and simple.
I have a MVVM (Prism) application that I need to implement a master details screen wheer the master is a listview and the details is displayed next to it. Read-only seems easy enough (haven't done it yet but I've got my head around WPF binding) but edit/add confuses me.
How to I make it so the master is not updated until the details is saved?
How do I make it so you can't change the master's current selection while in edit/add mode?
I've been googling a plenty but have not found any meaty examples of this.
Thanks.
PS: This view is a child view on a larger screen. This is why I want both master and detail together.
You certainly can do this, though in my opinion such a UI design fails to harness the full power of WPF. Old WinForms UIs usually didn't update most of the application until data was saved to SQL Server (or wherever) because they didn't have real business objects and a powerful binding system like WPF. Trying to copy WinForms limitations within WPF seems like a step backward to me. Why not show the latest data everywhere it is visible in the UI, including in the master view? Also, why not allow the user to edit multiple items before saving, for example marking any edited but unsaved item with an animated marker in the master view? Combine these with a generalized undo and you have a better design and more intuitive for the user.
However if your business requirements make it absolutely necessary, here is how to do it:
Preventing changes to data from being visible outside the detail until it is saved
Upon entry into your "edit/add mode", make a copy of the data objects and set your detail view's DataContext to the copy instead of the live object. When the data is "saved", copy the data from the shadow copy back into the live object and set your detail view's DataContext back where it should be.
Preventing the master's current selection from changing while in edit/add mode
Two possibilities:
During edit/add mode, change the master view to disallow mouse hit testing or keyboard focus
When edit/add mode begins, capture the "current selection" then add an event handler that watches for "current selection" changes and immediately changes the selection back to what it was. When edit/add mode ends, remove the handler. This handler can be conveniently coded using a lambda expression and using a closure on a local variable to store the current selection.
Thanks for the answer. Now I've re-read my message, I see it is rather vague. I have a screen that edits an object which contains multiple lists of other child objects. I've implemented these as different tabs in a tab control. One of these tabs edits the comments, so I wanted to display a list of comments with an edit panel for the current selection next to the list. The user could then use add, edit or delete buttons to update the list. I wanted to do this in a pure(ish) MVVM way.
I came up with the following design which seems to work with minimal hacks.
The View includes a list of the child objects simply as a ListView bound to an observable collection within the ViewModel. I included a child object buffer – this is used to buffer changes until they are ready to be saved back to the list (or thrown away).
The View also includes an edit panel bound to the buffer object in the ViewModel. The buffer is updated whenever the list view’s current selection changes using a deep copy. I tried using data binding on the Selecteditem property but the set was never called, so a small code-behind method was added to force the property to be updated when the selection was changed.
The list view and edit view are mutually exclusive. In theory you could hide the disabled one, perhaps using a flip screen. As a general pattern, it is better for my app to have both visible at the same time as the edit panel may show extra information not shown in the list view. The choice as to which panel is enabled is controlled by binding IsEnabled to a ViewModel property like IsEditCommentMode.
Commands to manage the list have to be added, these are New, Editand Delete. Note that Add and Edit will set set up the buffer then set IsEditCommentMode to true. These list management commands are only available when IsEditCommentMode is false.
The edit panel implements Save and Cancel commands, they are only be enabled when IsEditCommentMode is true. When Save is executed, it should copy from the buffer to the list (either add or update) and fire the change notification. Finally, it should set IsEditCommentMode to false.
This all works well and does not seem to violate any MVVM tenents (in my humble but often flawed opinion).