ImageSource Binding not showing - xamarin.forms

I have already tried to solve it via reddit but i did not find any solution there. I have following code which gets me and ImageSource from my AccountViewModel and sets the ProfileImage property on the CommentViewModel to the same Image. The Image is not available as a saved files since it comes from my Backend Server. I already chacked, that the ProfileImage Property actually gets the right image, it does, also the Property Changed event fires and if i set the Property to an ImageSource.FromFile with a test Image which i have saved as a file it works. I really don't see any reason why it would not work with the image i get from the other viewmodel.
As the Image is a normal Property mit [BindableProperty] Annotation in the AccountViewModel and i have confirmed that the Types are exactly the same i do not show the AccoutnViewModel here to make it a bit shorter.
I do not get why the Start of every CodeBlock looks so strange.
CommentView:
<ContentPage.Content>
<StackLayout Margin="15">
<StackLayout Orientation="Horizontal">
<ffimageloading:CachedImage Source="{Binding ProfileImage, FallbackValue=default_user.jpg}"
HeightRequest="50"
WidthRequest="50"
VerticalOptions="CenterAndExpand"
HorizontalOptions="StartAndExpand">
<ffimageloading:CachedImage.Transformations>
<fftransformations:CircleTransformation/>
</ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>
<Label Text="{Binding Username}"
FontSize="13"/>
</StackLayout>
</StackLayout>
</ContentPage.Content>
CommentViewModel:
public partial class CommentViewModel : BaseViewModel
{
// == constants ==
// == observable properties ==
[ObservableProperty]
public long id;
[ObservableProperty]
public string username;
[ObservableProperty]
public string description;
[ObservableProperty]
ImageSource profileImage;
partial void OnProfileImageChanged(ImageSource value)
{
Console.WriteLine("profile changed");
}
// == constructors ==
public CommentViewModel(DisplayPostViewModel displayPostViewModel)
{
//profileImage = ImageSource.FromFile("default_user.jpg");
navigationService.DataBetweenViewModel<AccountViewModel>(this, "ProfileImage", "ProfileImage", true);
//profileImage = ImageSource.FromFile("default_user.jpg");
}
public CommentViewModel()
{
}
}
NavigationService:
public bool DataBetweenViewModel<ReceivingViewModel>(BaseViewModel sendingViewModel, string sendingPropertyName = null, string receivingPropertyName = null, bool isGettingFromOther = false)
where ReceivingViewModel : BaseViewModel
{
try
{
PropertyTransferObject transferObject;
var mainpage = Application.Current.MainPage as NavigationPage;
var tabbedPage = mainpage.RootPage as TabbedPage;
var recievingVM = tabbedPage.Children.SelectMany(tab => tab.Navigation.NavigationStack?
.Select(page => page.BindingContext)).OfType<ReceivingViewModel>();
if (isGettingFromOther)
{
transferObject = new PropertyTransferObject(recievingVM.First(), sendingViewModel, sendingPropertyName, receivingPropertyName);
}
else
{
transferObject = new PropertyTransferObject(sendingViewModel, recievingVM.First(), sendingPropertyName, receivingPropertyName);
}
objectMapper.TransferProperties(transferObject);
return true;
}
catch( Exception ex)
{
string e = ex.ToString();
return false;
}
}
ObjectMapper:
public void TransferProperties(PropertyTransferObject propertyTransferObject)
{
if (propertyTransferObject.SendingPropertyName != null && propertyTransferObject.ReceivingPropertyName != null
|| (propertyTransferObject.SendingPropertyName != String.Empty && propertyTransferObject.ReceivingPropertyName != String.Empty))
{
foreach (PropertyInfo recievingProp in propertyTransferObject.ReceivingObject.GetType().GetProperties())
{
foreach (PropertyInfo sendingProp in propertyTransferObject.SendingObject.GetType().GetProperties())
{
if (sendingProp.Name == propertyTransferObject.SendingPropertyName && recievingProp.Name == propertyTransferObject.ReceivingPropertyName)
{
recievingProp.SetValue(propertyTransferObject.ReceivingObject, sendingProp.GetValue(propertyTransferObject.SendingObject, null), null);
}
}
}
}
if (propertyTransferObject.SendingPropertyName == null && propertyTransferObject.ReceivingPropertyName == null
|| (propertyTransferObject.SendingPropertyName == String.Empty && propertyTransferObject.ReceivingPropertyName == String.Empty))
{
foreach (PropertyInfo recievingProp in propertyTransferObject.ReceivingObject.GetType().GetProperties())
{
foreach (PropertyInfo sendingProp in propertyTransferObject.SendingObject.GetType().GetProperties())
{
if (recievingProp.Name == sendingProp.Name && recievingProp.PropertyType == sendingProp.PropertyType)
{
recievingProp.SetValue(propertyTransferObject.ReceivingObject, sendingProp.GetValue(propertyTransferObject.SendingObject, null), null);
}
}
}
}
}

Related

ToList in MainViewModel

In a ViewModel i load data from a FirebaseDatabase and showing it in a CollectionView.
public MainViewModel()
{
var collection = firebase
.Child("Foto/")
.AsObservable<Fotos>()
.Subscribe((dbevent) =>
{
if (dbevent.Object != null)
{
Foto.Add(dbevent.Object);
}
});
}
But i want to change it ToList so i make it Descending based on BalId in the Database.
Want to use GetAllFotosDesending() but i cannot make it work in the MainViewModel.
public async Task<List<Fotos>> GetAllFotosDesending()
{
return (await firebase
.Child("Foto/")
.OnceAsync<Fotos>()).Select(item => new Fotos
{
BalId = item.Object.BalId,
RollNo = item.Object.RollNo,
Foto = item.Object.Foto,
Titel = item.Object.Titel,
Fototekst = item.Object.Fototekst
}).OrderByDescending(x => x.BalId).ToList();
}
2 options , or make it ToList in the MainViewModel or add GetAllFotosDesending() work in the MainViewModel.
The last option is maybe better ? but i cannot make this working when adding to the MainViewModel
This is the CollectionView with ItemsSource="{Binding Foto}"
<CollectionView
x:Name="Dood"
ItemsSource="{Binding Foto}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid ColumnDefinitions="Auto, *"
RowDefinitions="Auto, Auto, Auto, 1"
ColumnSpacing="10"
RowSpacing="5"
Padding="0,10">
<Image Source="{Binding Pic}"
Margin="20,0,0,10"
HeightRequest="70"
WidthRequest="70"
HorizontalOptions="Center"
VerticalOptions="Center"
Grid.RowSpan="3"
Grid.Row="0"
Grid.Column="0">
<Image.Clip>
<EllipseGeometry
Center="35,35"
RadiusX="35"
RadiusY="35"/>
</Image.Clip>
</Image>
<Label Text="{Binding Titel}"
FontAttributes="Bold"
Grid.Column="1"
Grid.Row="0"/>
<Label Text="{Binding Email}"
Grid.Column="1"
Grid.Row="1"/>
<Label Text="{Binding RollNo}"
Grid.Column="1"
Grid.Row="2"/>
<BoxView Style="{StaticResource SeparatorLine}"
Grid.Column="0"
Grid.Row="3"
Grid.ColumnSpan="2"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Binding like this
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
OrderBy does not have Alphanumeric comparison by default, the default Orderby only compares Alphabets or Numbers not a combination of both to handle this you need to create your own Alphanumeric Comparator that does this for you (especially if these are string values)
You can use this basic comparator for both Ascending and Descending orders:
public class AlphanumComparator : IComparer<object>
{
private enum ChunkType { Alphanumeric, Numeric };
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
return (type != ChunkType.Alphanumeric || !char.IsDigit(ch))
&& (type != ChunkType.Numeric || char.IsDigit(ch));
}
public int Compare(object x, object y)
{
string firstString = x as string;
string secondString = y as string;
if (string.IsNullOrWhiteSpace(firstString) || string.IsNullOrWhiteSpace(secondString))
{
return 0;
}
int firstMarker = 0, secondMarker = 0;
while ((firstMarker < firstString.Length) || (secondMarker < secondString.Length))
{
if (firstMarker >= firstString.Length)
{
return -1;
}
else if (secondMarker >= secondString.Length)
{
return 1;
}
char firstCh = firstString[firstMarker];
char secondCh = secondString[secondMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((firstMarker < firstString.Length) && (thisChunk.Length == 0 || InChunk(firstCh, thisChunk[0])))
{
thisChunk.Append(firstCh);
firstMarker++;
if (firstMarker < firstString.Length)
{
firstCh = firstString[firstMarker];
}
}
while ((secondMarker < secondString.Length) && (thatChunk.Length == 0 || InChunk(secondCh, thatChunk[0])))
{
thatChunk.Append(secondCh);
secondMarker++;
if (secondMarker < secondString.Length)
{
secondCh = secondString[secondMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
int thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
int thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
Once you are done, you use it like below:
OrderByDescending(x => x.BalId, new AlphanumComparator());
OrderBy(x => x.BalId, new AlphanumComparator());
Solved like it this.
public async Task<List<Fotos>> GetAllFotosDesending()
{
return (await firebase
.Child("Foto/")
.OnceAsync<Fotos>()).Select(item => new Fotos
{
BalId = item.Object.BalId,
RollNo = item.Object.RollNo,
Foto = item.Object.Foto,
Titel = item.Object.Titel,
Fototekst = item.Object.Fototekst
}).OrderByDescending(x => x.BalId).ToList();
}
public async void InitializeAsync()
{
Fotos = await GetAllFotosDesending();
}
public MainViewModel()
{
InitializeAsync();
}

"MultiSelection" not working properly when using "Server Side Filtering" MudBlazor MudTable in Blazor WebAssembly

I started using MudBlazor in a Blazor WebAssembly project.
The problem is that when I use server side filtering and pagination in MudTable, MultiSelection not working properly. I can select all the rows by clicking "Select All" checkbox but selectAll checkbox remains unchecked when All are selected; and unable to unselect all again.
Unselect only working 1 by 1.
I am using this link:
https://try.mudblazor.com/snippet/mEQcYHEKpSAoCWSn
I appreciate your helps.
If you change the code like this it works.
<MudTable ServerData="#(new Func<TableState, Task<TableData<Element>>>(ServerReload))"
#ref="table"
CustomHeader="true"
#bind-SelectedItems="selectedItems1"
MultiSelection="true"
RowClassFunc="#SelectedRowClassFunc"
OnRowClick="RowClickEvent"
RowsPerPageChanged="OnRowsPerPageChanged"
T="Element">
<ToolBarContent>
<MudText Typo="Typo.h6">Periodic Elements</MudText>
<MudSpacer />
<MudTextField T="string" ValueChanged="#(s=>OnSearch(s))" Placeholder="Search" Adornment="Adornment.Start"
AdornmentIcon="#Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
</ToolBarContent>
<HeaderContent>
<MudTHeadRow IgnoreCheckbox="true">
<MudTh>
<MudCheckBox T="bool" Checked="IsSelectAllChecked" CheckedChanged="#Select"></MudCheckBox>
</MudTh>
<MudTh><MudTableSortLabel SortBy="new Func<Element, object>(x=>x.Number)">Nr</MudTableSortLabel></MudTh>
<MudTh>Sign</MudTh>
<MudTh><MudTableSortLabel InitialDirection="SortDirection.Ascending" SortBy="new Func<Element, object>(x=>x.Name)">Name</MudTableSortLabel></MudTh>
<MudTh><MudTableSortLabel SortBy="new Func<Element, object>(x=>x.Position)">Position</MudTableSortLabel></MudTh>
<MudTh><MudTableSortLabel SortBy="new Func<Element, object>(x=>x.Molar)">Mass</MudTableSortLabel></MudTh>
</MudTHeadRow>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Nr">#context.Number</MudTd>
<MudTd DataLabel="Sign">#context.Sign</MudTd>
<MudTd DataLabel="Name">#context.Name</MudTd>
<MudTd DataLabel="Position">#context.Position</MudTd>
<MudTd DataLabel="Molar mass">#context.Molar</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText>No matching records found</MudText>
</NoRecordsContent>
<LoadingContent>
<MudText>Loading...</MudText>
</LoadingContent>
<PagerContent>
<MudTablePager PageSizeOptions="new int[]{1, 5, 10}" />
</PagerContent>
<FooterContent>
<MudTd colspan="5">Select All</MudTd>
</FooterContent>
</MudTable>
<MudText >#($"{selectedItems1.Count} items selected")</MudText>
<MudText Inline="true">Selected items: #(selectedItems1==null ? "" : string.Join(", ", selectedItems1.OrderBy(x=>x.Sign).Select(x=>x.Sign)))</MudText>
#code {
private IEnumerable<Element> pagedData;
private MudTable<Element> table;
private int totalItems;
private string searchString = null;
private List<string> clickedEvents = new();
private HashSet<Element> selectedItems1 = new HashSet<Element>();
private async Task<TableData<Element>> ServerReload(TableState state)
{
IEnumerable<Element> data = await httpClient.GetFromJsonAsync<List<Element>>("webapi/periodictable");
await Task.Delay(300);
data = data.Where(element =>
{
if (string.IsNullOrWhiteSpace(searchString))
return true;
if (element.Sign.Contains(searchString, StringComparison.OrdinalIgnoreCase))
return true;
if (element.Name.Contains(searchString, StringComparison.OrdinalIgnoreCase))
return true;
if ($"{element.Number} {element.Position} {element.Molar}".Contains(searchString))
return true;
return false;
}).ToArray();
totalItems = data.Count();
switch (state.SortLabel)
{
case "nr_field":
data = data.OrderByDirection(state.SortDirection, o => o.Number);
break;
case "sign_field":
data = data.OrderByDirection(state.SortDirection, o => o.Sign);
break;
case "name_field":
data = data.OrderByDirection(state.SortDirection, o => o.Name);
break;
case "position_field":
data = data.OrderByDirection(state.SortDirection, o => o.Position);
break;
case "mass_field":
data = data.OrderByDirection(state.SortDirection, o => o.Molar);
break;
}
pagedData = data.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray();
return new TableData<Element>() {TotalItems = totalItems, Items = pagedData};
}
private void OnRowsPerPageChanged(int pageSize)
{
selectedItems1.Clear();
}
private void OnSelectedItemsChanged(HashSet<Element> elements)
{
clickedEvents.Add("Selected items changed");
}
private void OnSearch(string text)
{
searchString = text;
table.ReloadServerData();
}
private void RowClickEvent(TableRowClickEventArgs<Element> tableRowClickEventArgs)
{
clickedEvents.Add("Row has been clicked");
}
private string SelectedRowClassFunc(Element element, int rowNumber)
{
return selectedItems1.Contains(element) ? "selected" : string.Empty;
}
private bool IsSelectAllChecked
{
get
{
var currentPage = table.CurrentPage;
var rowsPerPage =table.RowsPerPage;
var currentPageItems = table.FilteredItems.Skip(currentPage * rowsPerPage).Take(rowsPerPage);
if (!selectedItems1.Any(x => currentPageItems.Any(y => x == y)))
{
return false;
}
else
{
return true;
}
}
}
private void Select()
{
var currentPage = table.CurrentPage;
var rowsPerPage = table.RowsPerPage;
var currentPageItems = table.FilteredItems.Skip(currentPage * rowsPerPage).Take(rowsPerPage);
if (!selectedItems1.Any(x => currentPageItems.Any(y => x == y)))
{
foreach(var item in currentPageItems)
{
selectedItems1.Add(item);
}
}
else
{
foreach(var item in currentPageItems)
{
selectedItems1.Remove(item);
}
}
}
}

How to reference and bind a property at the same time?

Here is what I mean by the title. I have an Entry with a property called "IsValid" (bool) which is bound to a behaviour (it is called Validator) which checks if the input is in range (0 - 10 in this case) and colour the background of the entry red or transparent. That works fine. However, as I have the same logic in my ViewModel (to check if the input is in range) and show a dialog message if it is not, I wanted to directly bind to the validator's IsValid and use the bind fild in my viewModel (IsBinReferenceValid) abd thus remove the locig from the vm. Currently, the property in my vm IsBinReferenceValid is not changed in any way which indicates that the binding does not work.
Here is the xaml code:
<userControl:DetailedEntry
PlaceholderLabel="{x:Static locale:BinPrintLang.BinRef}"
Text="{Binding BinTextEntry}"
TextColor="{StaticResource PrimaryColor}"
BgColor="White"
BorderColor="{StaticResource DisableColor}"
VerticalOptions="CenterAndExpand"
IsLabelVisible="True"
Label="Bin Reference"
IsImportant="True"
IsValid="{Binding Source={x:Reference InputLengthValidator}, Path=IsValid}">
<userControl:DetailedEntry.EntryBehavior>
<ui:InputLengthValidator x:Name="InputLengthValidator"
MinValue="0"
MaxValue="10"
IsValid="{Binding Source=IsBinReferenceValid, Mode=OneWayToSource}"/>
</userControl:DetailedEntry.EntryBehavior>
</userControl:DetailedEntry>
Any ideas how I can reference and bind to a property at the same time, is that even possible (That is, if that is where the problem is coming from)?
Base validator code:
public class ValueInRangeValidator : Validator<Entry>
{
private static BindableProperty MinValueProperty =
BindableProperty.Create("MinValue", typeof(decimal?), typeof(ValueInRangeValidator));
public decimal? MinValue
{
get { return (decimal?) GetValue(MinValueProperty); }
set
{
SetValue(MinValueProperty, value);
OnPropertyChanged();
}
}
public static BindableProperty MaxValueProperty =
BindableProperty.Create("MaxValue", typeof(decimal?), typeof(ValueInRangeValidator));
public decimal? MaxValue
{
get { return (decimal?) GetValue(MaxValueProperty); }
set
{
SetValue(MaxValueProperty, value);
OnPropertyChanged();
}
}
public virtual void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
decimal i = 0;
IsValid = decimal.TryParse(e.NewTextValue, out i);
IsValid = IsValid && (MinValue == null ? i >= decimal.MinValue : i >= MinValue);
IsValid = IsValid && (MaxValue == null ? i <= decimal.MaxValue : i <= MaxValue);
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += Bindable_TextChanged;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= Bindable_TextChanged;
}
}
InputLengthValidator code:
public class InputLengthValidator : ValueInRangeValidator
{
public override void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
var max = (int) MaxValue;
var min = (int) MinValue;
var textLenght = e.NewTextValue.Length;
IsValid = textLenght >= min && textLenght < max;
}
}
I managed to get the validation working by subscribing to another (custom) bindable property of my DetailedEntry control called IsWarning.
<userControl:DetailedEntry
rid.Row="1"
Grid.Column="0"
Label="{x:Static locale:GoodsReceiptLang.NumLabels}"
Text="{Binding NumberOfLabels, Mode=TwoWay}"
TextColor="{StaticResource PrimaryColor}"
Keyboard="Numeric"
IsImportant="True"
IsWarning="{Binding ShowWarning}">
</userControl:DetailedEntry>
My VM:
private bool CanPrint()
{
var errors = new List<string>();
ShowWarning = false;
if (SelectedPrinter == null)
errors.Add(CommonLang.SelectPrinterErrorMsg);
if (string.IsNullOrEmpty(NumberOfLabels) || !int.TryParse(NumberOfLabels, out int numLabels))
{
ShowWarning = true;
errors.Add(CommonLang.NotValidInput);
}
if (errors.Any())
{
ShowErrorMessage(string.Join(" ", errors.ToArray()));
return false;
}
return true;
}

Windows Metro Style app data binding

I have a requirement to load images from a folder in the project to a stackpanel. Under each image a name should also be shown. The image folder can change at any time and the number of images can also change.(with a maximum of 50 images) I want to know if I can use data binding to handle this. I thought of having image ID's, their sources and the name for each image in an XML so that I can change that XML file whenever the image folder changes, without changing the rest of the code. Is that feasible? If so how? Can someone please guide me? Thank you in advance.
One solution would be to use a Filepicker to let the user select the images inside the folder, and then bind the selected images to an Itemscontrol. That itemscontrol can then be put inside the Stackpanel. Here's a quick sample using that solution.
Here's the codebehind for picking the image files:
private List<EditableImage> availableImagesList = new List<EditableImage>();
private async void FilePicker_Clicked(object sender, RoutedEventArgs e)
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
//TODO: add supported image file types
openPicker.FileTypeFilter.Add("jpg,png,gif");
// We prompt the user to pick one or more files
IReadOnlyList<StorageFile> files = await openPicker.PickMultipleFilesAsync();
if (files.Count > 0)
{
availableImages.DataContext = null;
String fp = ""; // The path of the picked image
int index = availableImagesList.Count;
foreach (StorageFile file in files)
{
// Copying the selected image to local app data folder
//TODO: check if the selected file is actually and image
if (file != null )
{
StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.DisplayName + file.FileType, NameCollisionOption.ReplaceExisting);
fp = fileCopy.Path;
}
//Creating the image
CustomImage picToAdd = new CustomImage(index+1, file.DisplayName, fp);
//Adding the image as an UI element to the app bar
availableImagesList.Add(picToAdd);
}
availableImages.DataContext = availableImagesList;
}
}
The CustomImage model:
public class CustomImage
{
private static Uri _baseUri = new Uri("ms-appx:///");
private int _id;
public int Id
{
get { return _id; }
set
{
this.SetProperty(ref this._id, value);
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
this.SetProperty(ref this._name, value);
}
}
private string _imgPath;
public string ImgPath
{
get { return _imgPath; }
set
{
this.SetProperty(ref this._imgPath, value);
}
}
private String _imagePath = null;
private ImageSource _image = null;
public ImageSource Image
{
get
{
if (this._image == null && this._imagePath != null)
{
this._image = new BitmapImage(new Uri(CustomImage._baseUri, this._imagePath));
}
return this._image;
}
set
{
this._imagePath = null;
this.SetProperty(ref this._image, value);
}
}
public void SetImage(String path)
{
this._image = null;
this._imagePath = path;
this.OnPropertyChanged("Image");
}
public CustomImage(int id, string name, string imagepath)
{
SetImage(imagepath);
_id = id;
_name = name;
}
}
Here's the XAML for the ItemsControl inside the Stackpanel:
<StackPanel x:Name="loadedImages" HorizontalAlignment="Left" Orientation="Horizontal">
<!--Displaying the selected images in stackpanel-->
<ItemsControl ItemsSource="{Binding}" ItemsPanel="{StaticResource LoadedItemsPanel}">
<ItemsControl.ItemTemplate>
<!--The template for each object that is displayed as an UI element-->
<DataTemplate>
<Grid Height="88" Margin="2,0" Width="88" >
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
In your page resources, you must also define:
<ItemsPanelTemplate x:Key="LoadedItemsPanel">
<WrapGrid Orientation="Horizontal"/>
</ItemsPanelTemplate>

Save selected items when using caliburn.micro /Telerik RadGridView /Silverlight

I am using Caliburn micro(1.3)/MVVM and Silverlight. When I update the itemsource RadGridView, I lose the selected items. I found a blog about implementing a behavior to save the selected items when you are implementing MVVM. I can get the selected items, but I cannot set them back once the itemsource is refreshed. Can someoneshow me how to implement this using caliburn.micro and the RadGridVIew? I think the best way to go is to create a caliburn micro convention, but I can only find a reference for creating a convention for selectedItem, not selectedItems.
Can someone show me how to accomplish this? I tried the following, but it does not work.
private static void SetRadGridSelecteditemsConventions()
{
ConventionManager
.AddElementConvention<DataControl>(DataControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
ConventionManager.SetBinding(viewModelType, path, property, element, convention, DataControl.ItemsSourceProperty);
if (ConventionManager.HasBinding(element, DataControl.SelectedItemProperty))
return true;
var index = path.LastIndexOf('.');
index = index == -1 ? 0 : index + 1;
var baseName = path.Substring(index);
foreach (var selectionPath in
from potentialName in ConventionManager.DerivePotentialSelectionNames(baseName)
where viewModelType.GetProperty(potentialName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null
select path.Replace(baseName, potentialName))
{
var binding = new Binding(selectionPath) { Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(element, DataControl.SelectedItemProperty, binding);
}
return true;
};
}
Thanks,
Stephane
You should use a behavior for this since the SelectedItems property is readonly.
Telerik has an example for this, only the example is not specific for caliburn.micro.
If you add the following class to your project:
public class MultiSelectBehavior : Behavior<RadGridView>
{
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(MultiSelectBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((MultiSelectBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
}
void ContextSelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItems_CollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItems_CollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
This behavior takes care of the synchronization between collection RadGridView.SelectedItems and MultiSelectBehavior.SelectedItems.
Now we need to have an ObservableCollection in the ViewModel
//Collection holding the selected items
private ObservableCollection<object> selectedGridItems;
public ObservableCollection<object> SelectedGridItems
{
get
{
if (selectedGridItems == null)
selectedGridItems = new ObservableCollection<object>();
return selectedGridItems;
}
set
{
if (selectedGridItems == value) return;
selectedGridItems = value;
NotifyOfPropertyChange(() => SelectedGridItems);
}
}
//Deselect all selected items in the gridview
public void ClearSelectedGridItems()
{
SelectedGridItems.Clear();
}
Last thing is bind the behavior in the view
<telerik:RadGridView x:Name="CustomLogs" AutoGenerateColumns="true" SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectBehavior SelectedItems="{Binding SelectedGridItems}"/>
</i:Interaction.Behaviors>
</telerik:RadGridView>
Thats it, hope it helps you!

Resources