0

I have made an UserControl (let's call it Section) where user can change its layout based on value in combobox. In MainView I have ObservableCollection of those UserControls and also implemented command for moving them across this collection. Everything using MVVM pattern. The problem is every time I move an element, my layout is returning to default. In SectionModel's properties everything stays the same as it was, even combobox is displaying correct value.

I am changing layout using code-behind of the view based on value in combobox populated by MainViewModel.

I debug and trace the code starting from clicking "moving" button and it looks like compiler is creating new object of SectionView, but is not regrabing current layout format. So I think I need a way to regrab combobox value and run switch statement in View's code-behind.

PS. After changing format again in broken section everything displaying okay, data from properties were stored. Also I am using Prism if its matter.

GIF of the problem

MainViewModel

    public MainViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;

        Formats = new ObservableCollection<string>()
        {
            "Text",
            "Photo",
            "Text/Photo",
            "Photo/Text",
            "Photo/Photo"
        };
    }

    private ObservableCollection<SectionModel> _sections;
    public ObservableCollection<SectionModel> Sections
    {
        get { return _sections; }
        set { SetProperty(ref _sections, value); }
    }

    private ObservableCollection<string> _formats;
    public ObservableCollection<string>  Formats
    {
        get { return _formats; }
        set { SetProperty(ref _formats, value); }
    }

    private DelegateCommand<SectionModel> _moveSectionDown;
    public DelegateCommand<SectionModel> MoveSectionDown =>
        _moveSectionDown ?? (_moveSectionDown = new DelegateCommand<SectionModel>(ExecuteMoveDownSection)); /// there is also command for moving up :)
    void ExecuteMoveDownSection(SectionModel obj)
    {
        if (Sections.IndexOf(obj) < Sections.Count() - 1)
        {
            Sections.Move(Sections.IndexOf(obj), Sections.IndexOf(obj) + 1);
            RecalculateSectionsIndexes();
        }
        
    }

    private void RecalculateSectionsIndexes()
    {
        foreach (var section in Sections)
               section.SectionId = Sections.IndexOf(section) + 1;
    }

SectionModel

public class SectionModel : BindableBase
{
    [Key]
    public int Id { get; set; }

    private string _selectedFormat;
    public string SelectedFormat
    {
        get { return _selectedFormat; }
        set { SetProperty(ref _selectedFormat, value); }
    }
    
    public string Content { get; set; } = string.Empty;

    private OfferPhoto? _leftPhoto;
    public virtual OfferPhoto? LeftPhoto
    {
        get { return _leftPhoto; }
        set { SetProperty(ref _leftPhoto, value); }
    }

    public int RightPhotoId { get; set; }
    private OfferPhoto? _rightPhoto;
    public virtual OfferPhoto? RightPhoto
    {
        get { return _rightPhoto; }
        set { SetProperty(ref _rightPhoto, value); }
    }


    private int _sectionId;
    public int SectionId
    {
        get { return _sectionId; }
        set { SetProperty(ref _sectionId, value); }
    }
}

SectionView.xaml // I will simplify it for this post and show only needed controls

<ComboBox x:Name="cbSelectedFormat" MinWidth="130" Margin="0 10 0 10" ItemsSource="{Binding DataContext.Formats, ElementName=offerEditor}"
                          SelectedItem="{Binding SelectedFormat, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DropDownClosed="cbSelectedFormat_DropDownClosed"/>

<Button Width="{Binding ActualHeight, ElementName=buttonPanel}" BorderThickness="0" Command="{Binding DataContext.MoveSectionDown, ElementName=offerEditor}" CommandParameter="{Binding}">
                <fa:ImageAwesome Icon="Solid_ArrowCircleDown" Width="20" Opacity="0.6" Foreground="ForestGreen"/>
            </Button>

    <Grid x:Name="imageGrid" Grid.Row="1" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>

        <TextBox x:Name="tbContent" Margin="20"
                   Padding="5" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" 
                   TextWrapping="WrapWithOverflow" TextAlignment="Justify" FontFamily="Open Sans" FontSize="16" 
                   Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <Button x:Name="leftImage" Margin="20"
                   HorizontalAlignment="Center"
                   Command="{Binding DataContext.ChooseLeftPhotoCommand, ElementName=offerEditor}" CommandParameter="{Binding}">
            <Image>
                <Image.Style>
                    <Style TargetType="Image">
                        <Setter Property="Source" Value="{Binding LeftPhoto.PhotoUri, UpdateSourceTrigger=PropertyChanged}"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding LeftPhoto}" Value="{x:Null}">
                                <Setter Property="Source" Value="/AppNameAssembly;component/resources/images/placeholder_description_image.jpg"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>

            </Image>
        </Button>

        <Button x:Name="rightImage" Margin="20"
                   HorizontalAlignment="Center"
                   Command="{Binding DataContext.ChooseRightPhotoCommand, ElementName=offerEditor}" CommandParameter="{Binding}">
            <Image>
                <Image.Style>
                    <Style TargetType="Image">
                        <Setter Property="Source" Value="{Binding RightPhoto.PhotoUri, UpdateSourceTrigger=PropertyChanged}"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding RightPhoto}" Value="{x:Null}">
                                <Setter Property="Source" Value="/AppNameAssembly;component/resources/images/placeholder_description_image.jpg"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>
            </Image>
        </Button>
    </Grid>

I use buttons for holding images cause I can change image after clicking it.

SectionView.xaml.cs

public partial class SectionView : UserControl
{
    public SectionView()
    {
        InitializeComponent();
    }

    public string SelectedFormat { get; private set; } // This property is only for testing if view stored SelectedFormat - no it did not. After moving UserControl across the list SelectedFormat here is null, but in SectionModel is set correct.

    private void cbSelectedFormat_DropDownClosed(object sender, System.EventArgs e)
    {
        SelectedFormat = cbSelectedFormat.SelectedItem.ToString();
        if (SelectedFormat != null)
            switch (SelectedFormat)
            {
                case "Photo":
                    leftImage.Visibility = Visibility.Visible;
                    Grid.SetColumn(leftImage, 0);
                    Grid.SetColumnSpan(leftImage, 2);


                    tbContent.Visibility = Visibility.Collapsed;
                    rightImage.Visibility = Visibility.Collapsed;
                    break;


                case "Text":
                    leftImage.Visibility = Visibility.Collapsed;

                    tbContent.Visibility = Visibility.Visible;
                    Grid.SetColumn(tbContent, 0);
                    Grid.SetColumnSpan(tbContent, 2);

                    rightImage.Visibility = Visibility.Collapsed;
                    break;

                case "Text/Photo":
                    leftImage.Visibility = Visibility.Visible;
                    Grid.SetColumn(leftImage, 1);
                    Grid.SetColumnSpan(leftImage, 1);


                    tbContent.Visibility = Visibility.Visible;
                    Grid.SetColumn(tbContent, 0);
                    Grid.SetColumnSpan(tbContent, 1);

                    rightImage.Visibility = Visibility.Collapsed;
                    break;

                case "Photo/Text":
                    leftImage.Visibility = Visibility.Visible;
                    Grid.SetColumn(leftImage, 0);
                    Grid.SetColumnSpan(leftImage, 1);


                    tbContent.Visibility = Visibility.Visible;
                    Grid.SetColumn(tbContent, 1);
                    Grid.SetColumnSpan(tbContent, 1);

                    rightImage.Visibility = Visibility.Collapsed;
                    break;

                case "Photo/Photo":
                    leftImage.Visibility = Visibility.Visible;
                    Grid.SetColumn(leftImage, 0);
                    Grid.SetColumnSpan(leftImage, 1);

                    tbContent.Visibility = Visibility.Collapsed;

                    rightImage.Visibility = Visibility.Visible;
                    Grid.SetColumn(rightImage, 1);
                    Grid.SetColumnSpan(rightImage, 1);
                    break;
            }
    }
}

MainView (offerEditor)

        <ListBox Height="640" ItemsSource="{Binding Sections, UpdateSourceTrigger=PropertyChanged}" ScrollViewer.CanContentScroll="False" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Auto">
                <ListBox.ItemContainerStyle>
                    <Style TargetType="ListBoxItem">
                        <Setter Property="Padding" Value="0"/>
                        <Setter Property="Width" Value="auto"/>
                    </Style>
                </ListBox.ItemContainerStyle>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <uc:SectionView MaxWidth="{Binding ActualWidth, ElementName=sectionsMenuBar}" DataContext="{Binding}" ></uc:SectionView>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

I can't find any information about ObservableCollection<>.Move(int oldIndex, int newIndex,) method, but I suspect it might be it. Maybe this method is copying object, deleting old one and creating new one based on copy on new index in collection, I don't know.

Does somebody know what it could be or had similar problem? Thanks for help!

0 Answers0