How can I MVVM-Compliant modify dynamically the contenttype of a custom control?

enzyklopenzyklop DEMember ✭✭

Hi,

I am trying to make an own control with a headerline and a contentarea. The Problem: The Content is not always a String which I can use with a label. It could be quite every possible contenttype. For example a List or a boolean, in this cases my control should use instead of a label for example a listview or a checkbox or some aquivalent stuff.

Furthermore, I made it work with a simple Class, which I called BindableContentView : ContentView, where I made the content bindable. But then I need to Bind this Content in my ViewModel and for this I need to implemnt an UI-Element in my ViewModel... This is not MVVM-Compliant, isn't it?

Any suggestions so far?

Thanks for your help! :)

Best Answer

Answers

  • TorbenKruseTorbenKruse DEMember ✭✭✭

    Hi Timo,

    I hope I understood your question.

    I think what you could do is write some type of "ValueToViewConverter". Then bind your property of your ViewModel in the View and attach the IValueConverter, which converts the datatype to the corresponding View.

    There are probably some more solutions but that's the first thing came into my mind. Doing this should avoid having UI elements in your ViewModel.

  • enzyklopenzyklop DEMember ✭✭

    @TorbenKruse Thank you for your reply. this is not exactly what I was looking for. There are to many difficulties with the binding and to make a Type-Selection isn't that easy in C#... But the Idea with a Converter was interessting. Thank you! :)

    @adamkemp that's it! :) I am going to use the DataTemplate-Selector for multiple kinds of datatemplates and need to extend a Content-View with a bindable DataTemplate. This is the way I'm gonna try now. If it works I'll keep you posted! :D

  • batmacibatmaci DEMember ✭✭✭✭✭
    edited February 2016

    Do you guys have a sample usage of ContentControl from xlabs? I cant figure it out.
    Instead of xlabs ContentControl, cant we use ContentView from xamarin.forms library?

  • batmacibatmaci DEMember ✭✭✭✭✭

    I tried to use like that but It doesnt display anything. Can you please tell me what I am doing wrong?

    <xlabs:ContentControl ContentTemplate="{Binding ImagesView}"  BindingContext="{Binding DetailPageModel}"   ></xlabs:ContentControl>
    
    
            public StackLayout ImagesView { get; set; }
    
                StackLayout slImages = new StackLayout();
                    slImages.Orientation = StackOrientation.Horizontal;
                    foreach (Models.ExerciseImage.Result image in  Exercise.ExerciseImages)
                    {
                        var exerciseImage = new Image { Aspect = Aspect.AspectFit };
    
                        exerciseImage.Source= ImageSource.FromFile( "imgNotAvailable.png");
                        slImages.Children.Add(exerciseImage);
    
                    }
    
                    ImagesView = slImages;
                    RaisePropertyChanged("ImagesView");
    
  • adamkempadamkemp USInsider, Developer Group Leader mod

    Is the ImagesView property in the detail page model? That's where it will look.

  • batmacibatmaci DEMember ✭✭✭✭✭

    Yes it is but it is not working. I am not sure if the Binding should be like that. I cant find any example for the binding

  • batmacibatmaci DEMember ✭✭✭✭✭

    This is how my view and viewmodel looks like

        <?xml version="1.0" encoding="utf-8" ?>
        <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"      
                    xmlns:xlabs="clr-namespace:XLabs.Forms.Controls;assembly=XLabs.Forms"
                          xmlns:local="clr-namespace:myProject;assembly=myProject"
                     x:Class="myProject.DetailPage" >
    
          <ScrollView VerticalOptions="FillAndExpand">
    
            <Grid>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
              </Grid.ColumnDefinitions>
              <Grid.RowDefinitions>
                <RowDefinition Height="20" />
                <RowDefinition Height="20" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
              </Grid.RowDefinitions>
    
             <xlabs:ContentControl  ContentTemplate="{Binding ImagesView}"  BindingContext="{Binding DetailPageModel}"   Grid.Row="3" Grid.Column="0"></xlabs:ContentControl>
    
    
    
    
            </Grid>
    
          </ScrollView>
    
    
        </ContentPage>
    

    viewmodel

    using FreshMvvm;
    using Xamarin.Forms;
    
    namespace myProject
    {
       public class DetailPageModel : FreshBasePageModel
        {
          public StackLayout ImagesView { get; set; }
    
    
            IUserDialogs _userDialogs;
    
            public DetailPageModel(IUserDialogs userDialogs)
            {
               this._userDialogs = userDialogs;   
            }
    
            public override void Init(object initData)
            {
                base.Init(initData);
    
                _userDialogs.ShowLoading();
    
                setImages();
                _userDialogs.HideLoading();
            }
    
    
    
            private void setImages()
            {
                if (Images != null && Images.Count > 0)
                {
    
                    StackLayout slImages = new StackLayout();
                    slImages.Orientation = StackOrientation.Horizontal;
                   foreach (var image in  Exercise.ExerciseImages)
                    {
                        var exerciseImage = new Image { Aspect = Aspect.AspectFit };
    
                        exerciseImage.Source= ImageSource.FromFile( "imgNotAvailable.png");
                        slImages.Children.Add(exerciseImage);
    
                    }
                    ImagesView = slImages;
                    RaisePropertyChanged("ImagesView");
                }
            }
        }
    }
    

    I used fresh mvvm to bind view and viewmodel but i am not sure how to do it with xlabs contentcontrol.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I really can't help you without a complete example that I can run. I'm missing too much context, and there are too many different ways that this could be wrong.

  • batmacibatmaci DEMember ✭✭✭✭✭

    hi @adamkemp

    I added a simple test app including what I try to achieve. I want to display there an image inside the contentcontrol but I dont know how to achieve the binding. Image is working fine if you see simple image control which is commented on the xaml. Problem should be with the binding. please let me know. thank.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    The first problem is that, as expected, your binding doesn't make sense. BindingContext is an inherited property (inherited from the parent element). Your page has its BindingContext set to a TestPageModel, which means by default your ContentControl's BindingContext is also a TestPageModel by default. What you've done is set the ContentControl's BindingContext in XAML using a binding ({Binding TestPageModel}). Bindings are relative to the BindingContext (unless otherwise specified), which means in this case it's going to look for a property named "TestPageModel" on the instance of TestPageModel that you already have. Obviously TestPageModel doesn't actually have a property named "TestPageModel" so that binding doesn't work. The first step, then, is to delete the setting of the BindingContext in your XAML.

    The next problem is that your binding for the ContentTemplate property of the ContentControl is to a property named ImagesView, which has a type of StackLayout. Generally when you see a property named with a suffix "Template" then you should assume that the expected type to assign to that property is a DataTemplate, and indeed that's expected here. A DataTemplate is not itself a view, but instead is a way of constructing a view from a template. If you want to use ContentControl then you have to give it a template, not a view.

    To do this you would first change your model code (really a view model, so I'm not a fan of the name) like this:

        public DataTemplate ImagesTemplate { get; set; }
    
        public TestPageModel()
        {
            setImages();
        }
    
        private void setImages()
        {
            ImagesTemplate = new DataTemplate(() =>
                {
                    StackLayout slImages = new StackLayout();
                    slImages.Orientation = StackOrientation.Horizontal;
                    var Image = new Image { Aspect = Aspect.AspectFit };
                    Image.Source = ImageSource.FromFile("imgNotAvailable.png");
                    slImages.Children.Add(Image);
    
                    return slImages;
                });
    
            RaisePropertyChanged("ImagesView");
        }
    

    Then change your XAML like this:

    <xlabs:ContentControl ContentTemplate="{Binding ImagesTemplate}" Grid.Row="2" Grid.Column="0" />
    

    With those changes your app will show the image view.

    Now, it looks like maybe what you're trying to do eventually is to have multiple images shown, probably from some list. That list is likely to also come from a binding. The direction you're going here is not very in line with the MVVM pattern. What you're doing is putting view logic in a view model. The better approach would be to keep only the data (the list of images) in the view model and then put the view stuff in the view layer. In a case like this what you would want is either a ListView or something like the Xamarin.Forms.Labs RepeaterView (or the improved version shown here). The idea there is that you can define how each item in the bound list (the ItemsSource) should be displayed from within the view, and the view model only has to vend the list of items. That's what you want. You don't want to define any view stuff in the view model. If you find your view model class to have any direct references to view types then you're going in the wrong direction.

  • batmacibatmaci DEMember ✭✭✭✭✭

    @adamkemp thank you very much. this works fine. Iistview and repeaterview display vertically, right? I used contentcontrol with stacklayout to display horizontall. As I understand displaying lists horizontally is tricky in Xamarin.Forms.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    Since RepeaterView inherits from StackLayout you can change its orientation the same way you would a StackLayout.

  • RavindraKumarRavindraKumar USMember ✭✭

    You are the champ @adamkemp !!!
    Thanks a lot for your nice elaboration.

Sign In or Register to comment.