WPF Binding to Binding defined in code - c#

In order to create my GUI more dynamically I like to do a binding in XAML which I defined in Code:
Edit:
I do not want call SetBinding() in code. I want to set the binding in XAML.
Code:
public class SPSProperty
{
public string LabelContent { get; private set; }
public string PropertyPath { get; private set; }
public Binding Binding { get; private set; }
public SPSProperty (INotifyPropertyChanged viewModel,string propertyPath, string labelContent)
{
LabelContent = labelContent;
PropertyPath = propertyPath;
Binding = new Binding(propertyPath);
Binding.Source = viewModel;
}
}
ViewModel:
public class MainWindowViewModel:BindableBase
{
public SPSProperty Property { get; set; }
public MainWindowViewModel()
{
Property = new SPSProperty(this, "Test_Property", "Test Property");
}
private string _Test_Property;
public string Test_Property
{
get { return _Test_Property; }
set { SetProperty(ref _Test_Property, value); }
}
}
How can I use the binding in XAML?
TextBox Text="{Binding Property.Binding}" <=This does of course not work.

I created a Behaviour for my Textbox.
class DynamicBindingBehaviour: Behavior<TextBox>
{
public static readonly DependencyProperty DynamicBindingProperty =
DependencyProperty.Register("DynamicBinding", typeof(Binding), typeof(DynamicBindingBehaviour), new FrameworkPropertyMetadata());
public Binding DynamicBinding
{
get { return (Binding)GetValue(DynamicBindingProperty); }
set { SetValue(DynamicBindingProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SetBinding(TextBox.TextProperty, DynamicBinding);
}
}
And use it the in XAML by:
<TextBox DataContext="{Binding Path=Property}" >
<i:Interaction.Behaviors>
<local:DynamicBindingBehaviour DynamicBinding="{Binding Binding}"/>
</i:Interaction.Behaviors>
</TextBox>

Related

Populating a WPF ListBox based on a ComboBox Selection

I am trying to populate a WPF ListBox with data from a SQL Stored Procedure based on a ComboBox Selection. I've gotten the ComboBox to work like its supposed to, but I can't get the ListBox to display any data. My naming might be a little weird, but think of it as: the ComboBox gets all Recipes from SQL and the ListBox needs to display a list of Ingredients and their Amounts based on the users selection from that ComboBox. The API and Stored Procedures(...GetAll() for the ComboBox and GetByRationId() for the ListBox...) work, as I can retrieve the correct data using Swagger in the API and I can Populate the ComboBox and the RationId TextBlock in the UI, but I can't get the ListBox to show any data. I am still new to programming and I'm following tutorials etc. and I can't seem to find anything that speaks to my case specifically. I'm guessing I'm missing something. I've added the aforementioned TextBlock just to display the RationId, which is what needs to be used to get the correct data from SQL, as a test, just to make sure that the Id was getting through...and it is.
Here's the Xaml...
<StackPanel Grid.Column="1" Margin="50" Orientation="Vertical">
<ComboBox x:Name="FeedGroup" MinWidth="300" MinHeight="50"
SelectedItem="{Binding SelectedFeedGroup}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FeedGroupName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock x:Name="SelectedFeedGroup_RationId" Height="81"/>
<ListBox x:Name="FeedGroupRation" MinHeight="200" Padding="20" ItemsSource="{Binding SelectedFeedGroupRation}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10" HorizontalAlignment="Center">
<TextBlock Text="{Binding CommodityName}" FontSize="20" FontWeight="Bold"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="{Binding CommodityPercentage}" FontSize="16" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the ViewModel Class...
public class FeedGroupPageViewModel : Screen
{
IFeedGroupEndPoint _feedGroupEndPoint;
IFeedGroupRationEndPoint _feedGroupRationEndPoint;
IMapper _mapper;
private readonly StatusInfoViewModel _status;
private readonly IWindowManager _window;
public FeedGroupPageViewModel(IFeedGroupEndPoint feedGroupEndPoint,
IFeedGroupRationEndPoint feedGroupRationEndpoint,
IConfigHelper configHelper,
IMapper mapper,
StatusInfoViewModel status,
IWindowManager window)
{
_feedGroupEndPoint = feedGroupEndPoint;
_feedGroupRationEndPoint = feedGroupRationEndpoint;
_configHelper = configHelper;
_mapper = mapper;
_status = status;
_window = window;
}
protected override async void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
try
{
await LoadFeedGroup();
}
catch (Exception ex)
{
}
}
private async Task LoadFeedGroup()
{
var FeedGroupList = await _feedGroupEndPoint.GetAll();
var feedGroup = _mapper.Map<List<FeedGroupDisplayModel>>(FeedGroupList);
FeedGroup = new BindableCollection<FeedGroupDisplayModel>(feedGroup);
}
private BindableCollection<FeedGroupDisplayModel> _feedGroup;
public BindableCollection<FeedGroupDisplayModel> FeedGroup
{
get { return _feedGroup; }
set
{
_feedGroup = value;
NotifyOfPropertyChange(() => FeedGroup);
}
}
private FeedGroupDisplayModel _selectedFeedGroup;
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
private BindableCollection<FeedGroupRationModel> _feedGroupRation;
public BindableCollection<FeedGroupRationModel> FeedGroupRation
{
get { return _feedGroupRation; }
set
{
_feedGroupRation = value;
NotifyOfPropertyChange(() => FeedGroupRation);
}
}
private BindableCollection<FeedGroupRationModel> _selectedFeedGroupRation;
public BindableCollection<FeedGroupRationModel> SelectedFeedGroupRation
{
get { return _selectedFeedGroupRation; }
set
{
_selectedFeedGroupRation = value;
NotifyOfPropertyChange(() => SelectedFeedGroupRation);
}
}
}
And here are the Model Classes
public class FeedGroupDisplayModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public string FeedGroupName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime LastModified { get; set; }
public int RationId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FeedGroupRationModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string UserId { get; set; }
public int RationId { get; set; }
public string RationName { get; set; }
public int CommodityId { get; set; }
public string CommodityName { get; set; }
public int CommodityPercentage { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And Here are My Endpoint Classes
public class FeedGroupEndPoint : IFeedGroupEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupModel>> GetAll()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
and
public class FeedGroupRationEndPoint : IFeedGroupRationEndPoint
{
private IAPIHelper _apiHelper;
public FeedGroupRationEndPoint(IAPIHelper apiHelper)
{
_apiHelper = apiHelper;
}
public async Task<List<FeedGroupRationModel>> GetRationById()
{
using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync("/api/FeedGroup"))
{
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsAsync<List<FeedGroupRationModel>>();
return result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
I can add more info if needed. I've been working on this for quite awhile now and I'm just out of ideas. Any help would be greatly appreciated!
Thanks in advance!!
You don't seem to set the FeedGroupRation that the ListBox binds to somewhere.
I guess you want to fetch the items and set the property when the SelectedFeedGroup property is set. You could then hook up an event handler to the PropertyChanged event or override the NotifyOfPropertyChange method. Something like this:
public override async void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
base.NotifyOfPropertyChange(propertyName);
if (propertyName == nameof(FeedGroup))
{
//get the items...
var results = await ...;
//set the source property
FeedGroupRation = results;
}
}
As #Michal Davis comment stated I was missing a method for loading the ration, so I added LoadFeedGroupRation()...
private async Task LoadFeedGroupRation()
{
var _feedGroupRation = await _feedGroupRationEndPoint.GetRation();
var feedGroupRation = _mapper.Map<List<FeedGroupRationDisplayModel>>
(_feedGroupPenList);
FeedGroupRationList = new BindableCollection<FeedGroupRationDisplayModel>
(feedGroupRation);
}
Also based on #EldHasp's comment I updated the SelectedFeedGroup setter...
public FeedGroupDisplayModel SelectedFeedGroup
{
get { return _selectedFeedGroup; }
set
{
_selectedFeedGroup = value;
var FeedGroupRation = LoadFeedGroup
NotifyOfPropertyChange(() => SelectedFeedGroup);
}
}
I Don't know if this was the best way but I worked for my case.

How to set to XAML property a property value from another class

I'm trying to set property value from another class to XAML UI element property .
I have XAML and static class called "Config".
In Config class I have public static class Theme.
In the class Theme I have property primaryColor.
So I need to set primaryColor to UI element in XAML.
I tried x:Static, but it doesnt work for me, cause the field in the Theme class is not static.
XAML:
<StackLayout BackgroundColor={x:Static config:Config.CurrentTheme.primaryColor}></StackLayout>
Config.cs:
public static class Config
{
public static Theme CurrentTheme { get; set; }
}
Theme.cs:
public class Theme
{
public Color primaryColor { get; set; } = Color.FromHex("#1D1E1F");
public Color secondaryColor { get; set; } = Color.FromHex("#252625");
public Color grayColor { get; set; } = Color.FromHex("#2F2F2F");
public Color lightGrayColor { get; set; } = Color.FromHex("#626261");
public Color goldColor { get; set; } = Color.FromHex("#CAA440");
public Color lightGreenColor { get; set; } = Color.FromHex("#28A745");
public Color darkRedColor { get; set; } = Color.FromHex("#F0373A");
}
Adding Static to Theme.cs , then can be used in Xaml :
public static class Theme
{
public static Color primaryColor { get; set; } = Color.FromHex("#1D1E1F");
public static Color secondaryColor { get; set; } = Color.FromHex("#252625");
public static Color grayColor { get; set; } = Color.FromHex("#2F2F2F");
public static Color lightGrayColor { get; set; } = Color.FromHex("#626261");
public static Color goldColor { get; set; } = Color.FromHex("#CAA440");
public static Color lightGreenColor { get; set; } = Color.FromHex("#28A745");
public static Color darkRedColor { get; set; } = Color.FromHex("#F0373A");
}
Xaml :
<StackLayout BackgroundColor="{x:Static local:Theme.grayColor}"></StackLayout>
Don't forget to add local reference in xaml:
xmlns:local="clr-namespace:YourprojectNameSpace"
===================================Update====================================
If want to change color with binding property by viewmodel , have a try with IValueConverter:
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string valueAsString = value.ToString();
switch (valueAsString)
{
case (""):
{
return Color.Default;
}
case ("Accent"):
{
return Color.Accent;
}
default:
{
return Color.FromHex(value.ToString());
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Config.cs should be modified as follow:
public class Config : INotifyPropertyChanged
{
private string mycolor;
public string MyColor
{
get { return mycolor; }
set
{
mycolor = value;
OnPropertyChanged("MyColor");
}
}
public Config (){
mycolor = "#00FF00"; // can set a defalut color here
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in Xaml :
<ContentPage.Resources>
<ResourceDictionary>
<local:StringToColorConverter x:Key="StringToColorConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout BackgroundColor="{Binding MyColor, Converter={StaticResource StringToColorConverter}}"></StackLayout>
Finally , ContentPage need to bind Model Config.cs:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
Config config = new Config();
this.BindingContext = config;
//MyColor can be modified runtime
config.MyColor = "#00FF00";
}
}
When you read about x:Static it directly says:
x:Static accesses one of the following:
a public static field
a public static property
a public constant field
an enumeration member.
since your properties do not meet the criteria mentioned above it does not work!

Custom bindable stacklayout should trigger UI when item of ObservableCollection change

I have a problem with my custom stacklayout which populates the stacklayout correctly but does not recognizes any changes of any item in the bound observable collection..
This is the code I use for the bindable stacklayout:
public class BindableStackLayout : StackLayout
{
private readonly Label _header;
public BindableStackLayout()
{
_header = new Label();
Children.Add(_header);
}
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable),
typeof(BindableStackLayout), propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());
public DataTemplate ItemDataTemplate
{
get => (DataTemplate)GetValue(ItemDataTemplateProperty);
set => SetValue(ItemDataTemplateProperty, value);
}
public static readonly BindableProperty ItemDataTemplateProperty = BindableProperty.Create(nameof(ItemDataTemplate),
typeof(DataTemplate), typeof(BindableStackLayout));
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string),
typeof(BindableStackLayout), propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateHeader());
private void PopulateItems()
{
if (ItemsSource == null)
return;
foreach (var item in ItemsSource)
{
var itemTemplate = ItemDataTemplate.CreateContent() as Xamarin.Forms.View;
itemTemplate.BindingContext = item;
Children.Add(itemTemplate);
}
}
private void PopulateHeader() => _header.Text = Title;
}
Which is used like you can find here:
<ContentView.Content>
<h:BindableStackLayout ItemsSource="{Binding MenuHotKeys, Mode=TwoWay}"
Style="{StaticResource MenuControlStackLayout}">
<h:BindableStackLayout.ItemDataTemplate>
<DataTemplate>
<Button Text="{Binding DataA}"
Command="{Binding Path=BindingContext.MenuControlCommand, Source={x:Reference InternalMenuControl}}"
CommandParameter="{Binding .}"
Style="{StaticResource MenuControlButton}"/>
</DataTemplate>
</h:BindableStackLayout.ItemDataTemplate>
</h:BindableStackLayout>
</ContentView.Content>
And in the viewmodel I have this code:
private ObservableCollection<ConfigMenuItem> _menuHotKeys;
public ObservableCollection<ConfigMenuItem> MenuHotKeys
{
get => _menuHotKeys;
set => SetValue(ref _menuHotKeys, value);
}
And the change is here:
private async void MenuControlButtonPressed(object sender)
{
var menuItem = sender as ConfigMenuItem;
if (menuItem.ItemId == _expanderId)
{
// toggle expanded menu visibility
var expander = _menuHotKeys.FirstOrDefault(p => p.ItemId == _expanderId);
var buffer = expander.DataA;
expander.DataA = expander.DataB;
expander.DataB = buffer;
}
else
{
await NavigationHandler.NavigateToMenuItem(menuItem);
}
}
As you can see, I want to toggle the name of the bound button, but the changes does not appear.
I think I have to change something in bindable stacklayout class, but what?
Maybe you can help
#INPC answers:
The ConfigMenuItem in the Collection derives from:
public abstract class BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SetValue<T>(ref T field, T value, Expression<Func<T>> property)
{
if (!ReferenceEquals(field, value))
{
field = value;
OnPropertyChanged(property);
}
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> changedProperty)
{
if (PropertyChanged != null)
{
string name = ((MemberExpression)changedProperty.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
and the viewmodel derives from:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetValue<T>(ref T privateField, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(privateField, value))
{
privateField = value;
OnPropertyChanged(propertyName);
}
return;
}
}
As requested in comments the ConfigMenuItem class, code of BaseObject see in the code upside:
public class ConfigMenuItem : BaseObject, IConfigMenuItem
{
public int ItemId
{
get;
set;
}
public int Position
{
get;
set;
}
public string Name
{
get;
set;
}
public string DataA
{
get;
set;
}
public string DataB
{
get;
set;
}
public bool IsEnabled
{
get;
set;
}
public bool IsHotKey
{
get;
set;
}
public bool IsCustomMenuItem
{
get;
set;
}
public override string ToString()
{
return $"{Name} ({DataA} | {DataB ?? "null"})";
}
}
The problem is caused by the fact that although your ConfigMenuItem class derives from BaseObject, all its properties are plain properties and do not tirgger PropertyChanged event. You have to rewrite the properties to have a backing field and to trigger the event in their setter. For example:
private string _dataA;
public string DataA
{
get => _dataA;
set => SetValue(ref _dataA, value);
}
My example is using the SetValue method from BaseViewModel, and I actually think the BaseObject class is redundant and you could just use BaseViewModel instead. Using [CallerMemberName] for property is much more convenient than having additional logic for Expression<Func<T>>.

ObservableCollection not binding to combo box

Just wondering as to why my ObservableCollection is not binding to my Combo Box
I don't get any errors it just doesn't populate it.
public class TableList : ObservableCollection<TableName>
{
public TableList() : base()
{
Add(new TableName(1, "Notes"));
Add(new TableName(2, "TemplateNotes"));
}
}
public class TableName
{
private int noteID;
private string noteName;
public TableName(int ID, string name)
{
this.noteID = ID;
this.noteName = name;
}
public int NoteID
{
get { return noteID; }
set { noteID = value; }
}
public string NoteName
{
get { return noteName; }
set { noteName = value; }
}
}
This is my XAML
<ComboBox
x:Name="noteSaveToSelection"
HorizontalAlignment="Left"
Height="35"
Margin="155,932,0,0"
VerticalAlignment="Top"
Width="180"
ItemsSource="{Binding TableList}"
DisplayMemberPath="NoteName"
SelectedValuePath="NoteID"/>
I am new to this so i apologise if i have missed something small.
Apparently you never create an instance of your TableList class that you can actually bind to.
Create a view model class with a TableList property, e.g. like
public class ViewModel
{
public TableList TableList { get; } = new TableList();
}
Then set the DataContext property of your MainWindow to an instance of the view model class:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}

Binding TextBox to object WPF

I'm trying to make the TextBox show the stringBody property of the CurrentDialog property of the window. Here's the XAML part:
<TextBox x:Name="ComposerBox" Height="90.302" Margin="311,0,141.355,10"
...
Text="{Binding Body}"
ScrollViewer.CanContentScroll="True" SpellCheck.IsEnabled="True"
VerticalAlignment="Bottom">
Here is a string from the windows constructor:
MessagingBox.DataContext = CurrentDialog;
I've also tried to set DataContext to this with no result.
Here's how CurrentDialog is defined:
private MessageDialog CurrentDialog { get; set; }
Here's the MessageDialog class definition:
[Serializable][DataContract]
public class MessageDialog
{
public string Name { get; private set; }
public UserData User { get; private set; }
private List<Message> Dialog = new List<Message>();
public string Body { get; private set; }
public MessageDialog(UserData data)
{
Name = data.Username;
User = data;
Body = "";
}
public void Add(Message msg)
{
Dialog.Add(msg);
Body += $"{msg.From}: {msg.Body} \n\n";
}
}
}
The binding doesn't work at all. I also want it to be one-way.
Text="{Binding CurrentPerson.Body}"
Not sure why the binding path contains CurrentPerson, when it should be CurrentDialog, but even that isn't supposed to be there. Since the DataContext is already set to CurrentDialog, you can simply bind the text to :
Text="{Binding Body}"
You need to implement INotifyPropertyChanged, so the WPF know when the property changed:
[Serializable][DataContract]
public class MessageDialog : INotifyPropertyChanged
{
#region public string Body
private string m_Body;
public string Body
{
get { return m_Body; }
private set
{
if (m_Body == value)
return;
m_Body = value;
this.NotifyPropertyChanged();
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}

Categories

Resources