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.
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!