This project is read-only.

UserControls

Nov 3, 2010 at 7:25 AM

Hi all,

Instead of using windows for details about a person, like in the examples, I've been using a UserControl that I embed into the main window by using a ContentPresenter. Is there are way I can hook-up UpdateControls so that I can select a person from a datagrid that then drops the filled-out UserControl onto the view?

e.g., (using citation rather than person) I have

<ContentPresenter DataContext="{Binding CitationDetails}" />

in my XAML and

        public CitationViewModel SelectedCitation
        { 
            get
            {
                return _navigationModel.SelectedCitation == null ? null : new CitationViewModel(_navigationModel.SelectedCitation);
            }
            set
            {
                _navigationModel.SelectedCitation = value != null
                    ? value.Citation
                    : null;
            }
        }

        public object CitationDetails
        {
            get { return SelectedCitation == null ?  null : ForView.Wrap(SelectedCitation); }
        }

in my code. When I run, the UserControl with the citation details nothing comes up. Does anyone know how I can set this up. Of course, I could just get rid of the UserControl all together and put all the content into the one view, but I'd rather not do that.

Thannks


Nov 3, 2010 at 12:33 PM

You can absolutely do that. There are just two things.

First, you don't need to wrap CitationDetails if the class that exposes that property is itself wrapped. The wrapper recursively wraps all properties. (This won't fix your problem, it's just cleaner.)

Second, the data template needs to be registered using the wrapped type, not the raw type. ForView.Wrap() creates an ObjectInstance<T>, even though the return type is "object". If you add your data template to the resource dictionary by the type T, then it won't be found. You'll just get the ToString() presentation.

Instead, use WrappedType to generate the key for the resource dictionary, like this:

    <Window.Resources>
        <u:WrappedType x:Key="HomeVM" Type="homePane:HomeViewModel"/>
        <DataTemplate DataType="{StaticResource HomeVM}">
            <homePane:HomeUserControl/>
        </DataTemplate>
        <u:WrappedType x:Key="ContractListVM" Type="contractHeaderListPane:ContractHeaderListViewModel"/>
        <DataTemplate DataType="{StaticResource ContractListVM}">
            <contractHeaderListPane:ContractHeaderListUserControl/>
        </DataTemplate>
    </Window.Resources>

Let me know if this works for you.

Nov 4, 2010 at 2:50 AM

Great. That worked fine, I was just missing the WrappedType. Looking good.

I have a new issue, however. When I change data in a field of the DataGrid, it updates the equivalent TextBox in the UserControl. Nice. However, when I change data in the TextBox, it doesn't update the field in the DataGrid.

I thought maybe it had something to do with DataGrids, so I tried with your example. Works fine. Any ideas?

At first I thought it might have something to do with what is discussed at http://updatecontrols.codeplex.com/Thread/View.aspx?ThreadId=208360. But I have it working in the example app, so now I'm not so sure.

Very promising tool, by the way. If I can get these little difficulties out of the way I'll be set to convert my whole app over to use it!

Nov 4, 2010 at 12:59 PM

It sounds like the row in the data grid is not wrapped. Setting the value from there still gets to the OnSet(), but without the wrapper the row doesn't have INotifyPropertyChanged.

Like I said before, the wrapper recursively wraps all of the properties. However, it stops when it finds a property that implements INotifyPropertyChanged or INotifyCollectionChanged. If you use an ObservableCollection<T> for your collection, then the items in the collection will not be wrapped. If that's what you've done, try changing it to List<T>.

I'm glad you are considering adopting the library for the whole project. As you've already discovered, it's possible to ease your way into it.

Nov 5, 2010 at 1:38 AM

Hi Michael,

I got it to work. I already had IEnumerable<T> as my collection type. However, like your examples, I was binding to the ViewModel (e.g. public IEnumerable<PersonViewModel> People). I changed that to bind direct to the Model (e.g., public IEnumerable<Person> People) and it started working.

I've looked through your example and my code and can't find any difference that would prevent mine from not working with the ViewModel. I probably should bind my DataGrids to a ViewModel, but I don't really care either way at the moment. So, I'll just go with this fix until I figure out what is different.

Anyways, brilliant little API. This will save me a lot of time in coding and bug fixing. I'm quite excited, it's not every day you stumble upon something that can prove to be so useful. Thanks

 

Patrick

 

Nov 10, 2010 at 4:55 AM

It turned out my ViewModels still implemented INotifyPropertyChange. Whoops.

I have another issue and I think this one is more problematic.

My UserControl has a databound ComboBox in it. It looks like:

<ComboBox SelectedItem="{Binding Path=ReferenceType}" ItemsSource="{Binding Path=ReferenceTypes}" DisplayMemberPath="Value" />

The ViewModel code looks like:

        public KeyValuePair<intstring> ReferenceType
        {
            get { return Citation.RTs.Single(o => o.Key == Citation.ReferenceTypeId); }
            set { Citation.ReferenceTypeId = value.Key; }
        }

        public IDictionary<int,string> ReferenceTypes
        {
            get { return Citation.RTs; }
        }

This all binds correctly the first time I view a citation. I can change the items in the ComboBox and it updates the ReferenceTypeId for the citation. It also updates all dependent variables. However, if I select a different citation from the DataGrid or I add a new citation to the list, it fails to set the initial SelectedItem. I know ReferenceTypeId is returning the correct value. The items are still databound, so i can change the ReferenceType and it changes correctly. It's just the initial setting of the SelectedItem that isn't working.

The whole view (including the DataGrid and the ConentControl of the selected citation that has the UserControl that contains the ComboBox) is inside a TabControl. If I navigate to a different tab and back, the SelectedItem is set in the ComboBox correctly.

It's as though everything works correctly the first time the screen is rendered, but as I change the source of the ContentControl it bugs out. The way I handle the ContentControl is:

<ContentControl Content="{Binding SelectedCitation}" />

With the UserControl.Resources to link the ViewModel and View as

        <u:WrappedType x:Key="CitationViewModel" Type="vm:CitationViewModel"/>
        <DataTemplate DataType="{StaticResource CitationViewModel}">
            <v:CitationView />
        </DataTemplate>

The Citations and SelectedCitations looks like:

        public IEnumerable<CitationViewModel> Citations
        {
            get 
            {
                return _literatureReview.Citations.Select(o => new CitationViewModel(o, _metaData));
            }
        }

        public CitationViewModel SelectedCitation
        { 
            get
            {
                return Citations.SingleOrDefault(o => o.Citation == _navigationModel.SelectedCitation);
            }
            set
            {
                _navigationModel.SelectedCitation = value != null
                    ? value.Citation
                    : null;
            }
        }

Is there something I'm missing? I'm at a complete loss as to what could be causing this to stuff-up.

Nov 12, 2010 at 3:39 AM

Okay, I think I'm vaguely figuring out what is wrong. It might be a bug in UpdateControls, though I'm not sure. If I only add one citation, it works correctly. I change an item in the ComboBox it selects the new ReferenceType without issues.

However, if I add another citation then select an item in the ComboBox, it sets the ReferenceType but then, for some reason, calls SelectedCitation straight after that, then sets ReferenceType back to null, so nothing appears to be selected in the ComboBox. I don't know why it sets ReferenceType back to null. In another area of my code I do basically the same thing, but doesn't set anything back to null. I don't know why it does it in one situation and not the other.

Nov 12, 2010 at 5:59 PM
Edited Nov 12, 2010 at 6:02 PM

Sorry it's taken so long to respond. I've been traveling.

I've noticed this behavior in Silverlight, but WPF seems to be better. I was able to reproduce the problem in Silverlight without Update Controls. If that's the same issue you are seeing, try protecting ReferenceType against null assignment.

Another solution you can try is to bind the ItemsSource of the combo box to something outside of the DataContext. I've found that when the ItemsSource changes, Silverlight does a few too many sets on the SelectedItem. But if the ItemsSource is not affected by a change in DataContext, then it settles down. For example:

ItemsSource="{Binding ElementName=parent, Path=DataContext.ReferenceTypes}"

Where "parent" is the name of the root, not the details control. You'll have to move the ReferenceTypes property to the higher-level view model.

One last thing is that the elements of an ItemsSource collection need to implement Equals and GetHashCode. You are using KeyValuePair, which may or may not. I prefer to always implement my own miniature view model class, rather than try to reuse an existing class. That may or may not be related.

 

Nov 15, 2010 at 4:38 AM

The solution was as simple as it was weird. I changed:

<ComboBox SelectedItem="{Binding Path=ReferenceType}" ItemsSource="{Binding Path=ReferenceTypes}" />

to

<ComboBox ItemsSource="{Binding Path=ReferenceTypes}" SelectedItem="{Binding Path=ReferenceType}" />

I had no idea that it mattered what order you specify your attributes in XAML.

Thanks for all the suggestions. What a pain in the butt it has been trying to figure this out.

Now to get back to changing all my code over to use Update Controls everywhere.