Caliburn Micro reload data on event - c#

While handling a CurrentLayoutIdChangedEvent event, the backend sends back the correct data, but the setter for the model is not called and because of this, the UI won't reflect the changes.
Following the usual pattern to setting values in the getter.
The properties:
private LayoutModel _layout;
private string _layoutName;
public LayoutModel Layout
{
get { return _layout; }
set { _layout = value; NotifyOfPropertyChange(() => LayoutName); }
}
public string LayoutName
{
get { return _layout == null ? "Not set" : _layout.Name; }
set { _layoutName = value; NotifyOfPropertyChange(() => FullArea); }
}
The event:
public void Handle(CurrentLayoutIdChangedEvent message) => PopulateLayout(message.LayoutId);
The function:
private void PopulateLayout(int layoutId)
{
if (layoutId > 0)
{
try
{
_layout = _dataProvider.GetLayoutById(layoutId);
}
catch (Exception ex)
{
_logger.Error(ex, "Invalid Layout returned with '{layoutId}', exiting.", layoutId);
}
}
else
{
_logger.Error("Invalid LayoutId, aborting.");
}
}
A part of the view:
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="{x:Static lang:Resources.Txt_LayoutName}" />
<TextBlock x:Name="LayoutName" Margin="10 0 0 0" />
</StackPanel>
When receiving the event the function is called and the data is loaded into the _layout, but the setter of the public property is not called, so the LayoutName won't change.
Not sure what am I missing, the initial data in the LayoutName ("Not set") is displayed properly.
Tried to introduce a new event after the dataProvider returns the data, which only calls the NotifyOfPropertyChange(() => Layout); - also not giving any errors or desired result.

Believe I understood your concern correctly The Setter of Property Layout, in which you are Notifying the property change of LayoutName is not called during PopulateLayout, because, you are assigning value to the private variable _layout. (Not the property Layout)
You need to replace following line
_layout = _dataProvider.GetLayoutById(layoutId);
with
Layout= _dataProvider.GetLayoutById(layoutId);
Or Notify the LayoutName from the PopulateLayout method
_layout = _dataProvider.GetLayoutById(layoutId);
NotifyOfPropertyChange(() => LayoutName);

Related

complexed Issue with hiding ListView Xamarin Forms

I have a search bar with the property Text binded to a string property in my ViewModel.
I also have Behaviors within the search bar so that every time the text is changed a search is done within a list of objects using NewTextValue passed to as the query string.
The issue I have is that, I make the ListView invisible until a non-empty string is passed to my Search/Filter command (obviously.. :)). I have tried to enforcing hiding the ListView for a couple scenarios e.g. if all text is removed from the search bar.
When an item is selected from the now visible list view I used that item to populate the Text property of my SearchBar, after which I cannot hide it within code. All attempts have failed and the ListView remains visible. Note: I explicity created a hide button separately and saw it worked so I am wondering if I cannot tie hiding the view with setting the searchbar Text property.
View
<SearchBar Text="{Binding SearchText}">
<SearchBar.Behaviors>
<prismBehaviors:EventToCommandBehavior EventName="TextChanged"
Command="{Binding FilterOccupationsListCommand}"
EventArgsParameterPath="NewTextValue"/>
</SearchBar.Behaviors>
</SearchBar>
<ListView ItemsSource="{Binding FilteredOccupations}" IsVisible="{Binding FilteredOccupationsVisible}" SelectedItem="{Binding Occupation, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Please Note : My ViewModel inherits from BaseViewModel which inherits INotifyPropertyChanged. SetProperty() is what notifies the property. This is quite common with MvvmCross, Prism etc.
ViewModel
public class MyViewModel : BaseViewModel
{
public DelegateCommand<string> FilterOccupationsListCommand { get; }
public MyViewModel()
{
FilterOccupationsListCommand = new DelegateCommand<string>(FilterOccupationsList);
}
private void FilterOccupationsList(string query)
{
if (!string.IsNullOrWhiteSpace(query))
{
FilteredOccupationsVisible = true;
var searchResult = Occupations.Where(x => x.Name.ToLower().Contains(query));
FilteredOccupations = new ObservableCollection<Occupation>(searchResult);
}
else
FilteredOccupationsVisible = false;
}
private Occupation _occupation;
public Occupation Occupation
{
get => _occupation;
set
{
SetProperty(ref _occupation, value);
SearchText = value.Name;
}
}
private string _name;
public string Name { get => _name; set => SetProperty(ref _name, value); }
private string _searchText;
public string SearchText
{
get => _searchText;
set {
SetProperty(ref _searchText, value);
FilteredOccupationsVisible = false;
}
}
private bool _filteredOccupationsVisible;
public bool FilteredOccupationsVisible { get => _filteredOccupationsVisible; set => SetProperty(ref _filteredOccupationsVisible, value); }
public ObservableCollection<Occupation> _filteredOccupations = new ObservableCollection<Occupation>();
public ObservableCollection<Occupation> FilteredOccupations { get => _filteredOccupations; set { SetProperty(ref _filteredOccupations, value); } }
}
If not using Behaviors in SearchBar , you can have a try with TextChanged method of itself.
<SearchBar x:Name="MySearchBar" Text="SearchText" TextChanged="SearchBar_TextChanged" />
In ContentPage , when text cheanged fire here :
MyViewModel myViewModel = new MyViewModel();
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
Console.WriteLine("new -- " + e.NewTextValue + "-- old -- " + e.OldTextValue);
Console.WriteLine("MyEntry --" + MySearchBar.Text);
//Here can invoke FilterOccupationsList of MyViewModel
myViewModel.FilterOccupationsList(MySearchBar.Text);
}
Else if using Command to do , you need to add isntance of ICommand in MyViewModel to invoke FilterOccupationsList.
public class MyViewModel : BaseViewModel
{
public ICommand FilterOccupationsListCommand { private set; get; }
...
public MyViewModel()
{
FilterOccupationsListCommand = new Command<string>((NewTextValue) =>
{
// Pass value to FilterOccupationsList.
Console.WriteLine("SearchBar new text --" + NewTextValue);
FilterOccupationsList(NewTextValue);
});
}
...
}

How can I use an array in a ViewModel?

My code looks like this right now with two lines of code for each message. The code works but if I have for example 30 messages that I can each give values to then I will need to have 60 lines of code just to declare everything:
string _msg1;
string _msg2;
public string Msg1 { get => _msg1; set => SetProperty(ref _msg1, value); }
public string Msg2 { get => _msg2; set => SetProperty(ref _msg2, value); }
and in C# I assign to these:
vm.Msg1 = "A";
vm.Msg2 = "B";
and in the XAML I bind my Text to Msg1 and another Text to Msg2
Can someone tell me how / if I can do this with array so that I would assign like this and hopefully so the assignment of the array can just be done in two lines instead of 2 lines for every single message:
vm.Msg[0] = "A";
vm.Msg[1] = "B";
For reference:
public class ObservableObject : INotifyPropertyChanged
{
protected virtual bool SetProperty<T>(
ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You can create a simple wrapper class with indexing that supports property change notification.
For example:
public class Messages : ObservableObject
{
readonly IDictionary<int, string> _messages = new Dictionary<int, string>();
[IndexerName("Item")] //not exactly needed as this is the default
public string this[int index]
{
get
{
if (_messages.ContainsKey(index))
return _messages[index];
//Uncomment this if you want exceptions for bad indexes
//#if DEBUG
// throw new IndexOutOfRangeException();
//#else
return null; //RELEASE: don't throw exception
//#endif
}
set
{
_messages[index] = value;
OnPropertyChanged("Item[" + index + "]");
}
}
}
And, create a property in view model as:
private Messages _msg;
public Messages Msg
{
get { return _msg ?? (_msg = new Messages()); }
set { SetProperty(ref _msg, value); }
}
Now you can set or update values as:
vm.Msg[0] = "A";
vm.Msg[1] = "B";
Bindings in XAML will be same as:
<Label Text="{Binding Msg[0]}" />
<Label Text="{Binding Msg[1]}" />
Sample usage code
XAML
<StackLayout Margin="20">
<Label Text="{Binding Msg[0]}" />
<Label Text="{Binding Msg[1]}" />
<Label Text="{Binding Msg[2]}" />
<Label Text="{Binding Msg[3]}" />
<Label Text="{Binding Msg[4]}" />
<Button Text="Trigger update" Command="{Binding UpdateMessage}" />
</StackLayout>
Code-behind, view-model
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var viewModel = new MainViewModel();
viewModel.Msg[0] = "Original message 1";
viewModel.Msg[1] = "Original message 2";
viewModel.Msg[2] = "Original message 3";
viewModel.Msg[3] = "Original message 4";
viewModel.Msg[4] = "Original message 5";
BindingContext = viewModel;
}
}
public class MainViewModel : ObservableObject
{
private Messages _msg;
public Messages Msg
{
get { return _msg ?? (_msg = new Messages()); }
set { SetProperty(ref _msg, value); }
}
public ICommand UpdateMessage => new Command(() =>
{
Msg[2] = "New message 3";
Msg[0] = "New message 1";
});
}
Arrays will not raise property changed event. You'll need to use an ObservableCollection that can raise an event when the collection has changed. However, this doesn't raise an event when the object inside the collection has changed it's value. You'll need to wrap your object, in this case a string, into a type that can raise property changed events.
Something like the following would work:
public class BindableValue<T> : INotifyPropertyChanged
{
private T _value;
public T Value
{ get => _value; set => SetProperty(ref _value, value); }
// INotifyPropertyChanged and SetProperty implementation goes here
}
private ObservableCollection<BindableValue<string>> _msg;
public ObservableCollection<BindableValue<string>> Msg
{ get => _msg; set => SetProperty(ref _msg1, value); }
you would be binding to Msg[0].Value, Msg[1].Value etc.,
Not entirely sure that I got the question, but as I understood the simplest way is this:
The Viewmodel:
Just bind to an ObservableCollection of strings, because it already implements INotifyCollectionChanged and INotifyPropertyChanged.
RelayCommand is just an implementation of ICommand and I'm assuming you have heard of them since you are doing WPF MVVM.
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class MainWindowViewmodel
{
public ObservableCollection<string> Messages { get; set; }
public MainWindowViewmodel()
{
Messages = new ObservableCollection<string>();
Messages.Add("My message!");
ChangeMessageCommand = new RelayCommand(ChangeMessageExcecute);
}
public RelayCommand ChangeMessageCommand { get; set; }
private void ChangeMessageExcecute() => Messages[0] = "NEW message!";
}
}
The View:
In the view you can just bind your Textblocks to the Elements of the ObservableCollection. When you press the button, the Command gets called and changes the message in the window.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Messages[0]}" HorizontalAlignment="Center"/>
<Button Content="Change Message" Command="{Binding ChangeMessageCommand}" Width="200"/>
</StackPanel>
</Grid>
</Window>
Kind regards,
misdirection
I Assume that your given example is running and working as expected (Atleast with 2 items)
View Code.
Assuming you want to show all the 30 messages as a list.
<ListView ItemsSource="{Binding MessagesArray}"/>
Also you should set the DataContext properly, Comment below if you need any help
View Model Code.
We are using an ObservableCollection instead of array. Since pure arrays doesn't support proper binding features.
private ObservableCollection<string> _messagesArray;
public ObservableCollection<string> MessagesArray
{
get { return _messagesArray; }
set { SetProperty(ref _messagesArray, value); }
}
Assigning Values
MessagesArray = new ObservableCollection<string>();
vm.MessagesArray.Add("A");
vm.MessagesArray.Add("B");
In the assignment code MessagesArray = new ObservableCollection<string>(); assigns a new object of ObservableCollection of String
If you are new to ObservableCollection think of this as an wrapper to string[], but not actually true
SetProperty method will tell the XAML View that a new collection is arrived, so the UI will rerender the list.
When you call vm.MessagesArray.Add("B"); internal logics inside the method Add will tell the XAML View a new item is added to the ObservableCollection so the view can rerender the ListView with the new item.
Update 27 October 2018
You can create your own array using any of the below ways. (Not all)
string[] dataArray = new string[30];
1. this will create an array with 30 null values
string[] dataArray = { "A", "B", "C" }; //Go up to 30 items
2. this will create an array with predefined set of values, you can go up to 30
string[] dataArray = Enumerable.Repeat<string>(String.Empty, 30).ToArray();
3. this will create an array with string which holds empty values, Instead of String.Empty you can put any string value.
Choose any of the above method
I recommend the last method, then you can assign that into a Observable Collection like below.
MessagesArray = new ObservableCollection<string>(dataArray);
Now the trick is
vm.MessagesArray[0] = "A"
vm.MessagesArray[25] = "Z"
View might look like below
<TextBlock Text="{Binding MessagesArray[0]}"/>
<TextBlock Text="{Binding MessagesArray[1]}"/>
What about using reflection?
You can ask for all the public properties of type string with name "Msg*".
For example:
static class Program
{
static void Main(string[] args)
{
var vm = new MessagesViewModel();
PropertyInfo[] myProperties = vm.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.PropertyType == typeof(string) && p.Name.Contains("Msg"))
.ToArray();
foreach (var propertyInfo in myProperties)
{
//You can also access directly using the indexer --> myProperties[0]..
propertyInfo.SetValue(vm, $"This is {propertyInfo.Name} property");
}
Console.WriteLine(vm.Msg1);
Console.WriteLine(vm.Msg2);
}
}
public class MessagesViewModel
{
string _msg1;
string _msg2;
public string Msg1 { get => _msg1; set => _msg1 = value; }
public string Msg2 { get => _msg2; set => _msg2 = value; }
}
If this type of solution fits, you can wrap it with an indexer, sort the array to match the index and the Msg[num].

What could cause a binding to no update the source

I have a ComboBox defined like this:
<ComboBox
ItemsSource="{Binding Choices}"
SelectedItem="{Binding Value}"
Text="{Binding Text}"
IsEditable="True"
TextSearch.TextPath="Label"
DisplayMemberPath="Label" />
Here is my view Model:
public class ComboBoxViewModel : ViewModelBase
{
private string _selectedCode;
public ReadOnlyObservableCollection<ComboBoxItem> Choices { get; }
public ComboBoxItem Value
{
get { return this.Choices.FirstOrDefault(choice => choice.Code == _selectedCode); }
set
{
this.SetCode(value?.Code)
}
}
public string Text
{
get { return this.Value?.Label ?? _selectedCode; }
set
{
// Only set the code if no pre-defined code can be selected
if (this.Value == null)
{
this.SetCode(value)
}
}
}
public ComboBoxViewModel()
{
this.Choices = [..];
}
public bool SetCode(string code)
{
if (_selectedCode != code)
{
_selectedCode = code;
// Tried all the combination with/without/different order with no change
this.RaisePropertyChanged(nameof(this.Value));
this.RaisePropertyChanged(nameof(this.Text));
}
}
}
public class ComboBoxItem
{
public string Code { get; }
public string Label { get; }
public ComboBoxItem(string code, string label)
{
this.Code = code;
this.Label = label;
}
}
The Choices collection is initialized with some pair: Code,Label. I want to display the Label to the user and use the Code in my business layer. I also want my user to input its own code in the ComboBox (this is why the IsEditable dependency property is set to True and why I also bind Text on my ViewModel).
Everythings works fine when directly bind my ViewModel on the Control. The _selectedCode is updated prioritary with the selected Choices element or with the manual input if necessary.
My problem occurs when I pre-set the _selectedCode using the SetCode method. The Value property is no longer updated when I chose a new existing Choice in the ComboBox...
Is it possible to bind both SelectedItem and Text of a ComboBox? Do you have an idea why the bound properties are not updated after a programmatic initialization? It is like the event is not fired anymore...

Value Converter not working with caliburn.micro

I'm building a windows8.1 app and trying to tamper with the ConventionManager of caliburn.micro . I want to enable name convention binding with ValueConverters.
More specifically I have a collection of ProductViewModels that have two properties Value and IsValid. I want to bind the Value property to a textblock and the IsValid property to the BorderColorBrush of a Border via a ValueConverter.
Here's my code inside Configure in app.xaml.cs
var oldApplyConverterFunc = ConventionManager.ApplyValueConverter;
ConventionManager.ApplyValueConverter = (binding, bindableProperty, property) =>
{
if (bindableProperty == Control.BorderBrushProperty && typeof(bool).IsAssignableFrom(property.PropertyType))
// ^^^^^^^ ^^^^^^
// Property in XAML Property in view-model
binding.Converter = booleanToBrush;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// My converter used here. Code never reaches this point
// else I use the default converter
else if (bindableProperty == TextBlock.TextProperty && typeof(int).IsAssignableFrom(property.PropertyType))
{
//Code reaches this point when it should.
oldApplyConverterFunc(binding, bindableProperty, property);
}
else
{
oldApplyConverterFunc(binding, bindableProperty, property);
}
};
The problem is that the program never enters the first if clause. Does anybody know why???
My VM and xaml are these.
public class ProductViewModel
{
public ProductViewModel(int value)
{
_value = value;
}
private int _value;
public int Value
{
get { return _value; }
set
{
_value = value;
}
}
private bool _isValid;
public bool IsValid
{
get
{
_isValid = some validation logic;
return _isValid;
}
}
}
the view is a user control
<Border x:Name="IsValid">
<TextBlock x:Name="Value"/>
</Border>

Confused as to where to place logic code in ViewModel

I'm new to C#/WPF and I would like some clarification on whether I have the proper implementation of my ViewModel.
I have created a simple window with a search text box and list box for the results.
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListBox ItemsSource="{Binding Results}" />
Then I have a ViewModel with the following code.
private List<string> lstStr;
public ViewModel()
{
lstStr = new List<string>();
lstStr.Add("Mike");
lstStr.Add("Jerry");
lstStr.Add("James");
lstStr.Add("Mikaela");
}
public List<string> LstStr
{
get
{
return lstStr;
}
set
{
if (lstStr != value)
{
lstStr = value;
OnPropertyChanged("LstStr");
}
}
}
private string searchText;
public string SearchText
{
get
{
return searchText;
}
set
{
if (searchText != value)
{
searchText = value;
OnPropertyChanged("SearchText");
UpdateResults();
}
}
}
private ObservableCollection<string> results = new ObservableCollection<string>();
public ObservableCollection<string> Results
{
get
{
return results;
}
set
{
if (results != value)
{
results = value;
OnPropertyChanged("Results");
}
}
}
public void UpdateResults()
{
int i = 0;
results.Clear();
while (i < LstStr.Count)
{
if (LstStr.ElementAt(i).ToString() != null)
{
if (searchText != null && searchText != "")
{
if (LstStr.ElementAt(i).Trim().Contains(searchText))
{
results.Add(LstStr.ElementAt(i));
Console.WriteLine(LstStr.ElementAt(i));
}
}
else
results.Clear();
}
else
Console.WriteLine("NULL");
i++;
}
}
I see myself writing logic in the Get or Set section of code in the ViewModel. Let's say I will have more text boxes and lists that will want to implement. Is this the correct way of coding my logic in the properties or am I completely missing the point? Please help me understand this. Thanks in advance.
No, this isn't exactly right.
First, logic normally goes in the model, not the view model. That said, you have a filter, which is basically UI logic, so its probably OK here.
Second, the filter will only change when you set the search text, so the logic would go in the setter, not the getter. I also wouldn't inline the whole thing, put it in its own function so you can reuse it later:
public String SearchText
{
...
set
{
serachText = value;
NotifyPropertyChanged();
UpdateResults();
}
}
public void UpdateResults()
{
...
}
The one thing to keep in mind (and there isn't really a good way around this) is that if that function takes a long time to run, your UI will really slow down while the user is typing. If the execution time is long, try shortening it, then consider doing it on a separate thread.
ViewModels should have only the responsibility of "converting" data into another form that the view can handle (think INotifyPropertyChanged, ObservableCollection, etc.)
The only time where you'd get away with the ViewModel having any of the logic is when the logic is encapsulated entirely in a collection. e.g. if you can get everything you need out of List<T>, then the ViewModel effectively has all the logic. If you need value beyond that, it should be outside of the ViewModel.

Categories