This is my first time posting a question. I'm looking into this issue for about a whole day but cannot see why this binding doesn't work.
I want a Label to display the name of a object "hotspot" which is a Property of class instance named Plan. There are multiple plans and each plan contains multiple hotspots. When I click on a hotspot the property Plan.SelectedHotSpot sets this clicked hotspot as value. If there is no HotSpot selected it turns to null.
XAML:
<Label Name="lblHotSpotName" />
MainWindow code behind when Plan is selected from ListBox:
private void lstPlans_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
canvas.Plan = PlanBLL.GetPlanByID(plans[lstPlans.SelectedIndex].ID);
lblHotSpotName.DataContext = canvas.Plan;
lblHotSpotName.SetBinding(Label.ContentProperty, "SelectedHotSpot.Name");
}
Plan class:
public class Plan : INotifyPropertyChanged
{
private HotSpot selectedHotSpot;
public HotSpot SelectedHotSpot
{
get { return selectedHotSpot; }
set
{
selectedHotSpot = value;
OnPropertyChanged("SelectedHotSpot");
OnPropertyChanged("SelectedHotSpot.Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This code doesn't seem to work when I click on a hotspot lblHotSpotName stays empty.
It seems to me that when a plan is loaded SelectedHotSpot is null and so it doesn't bind to that hotspot object which is selected after the plan has been loaded.
Is my insinuation right? That this binding needs to have an existing object which is not null. And when the object changes that we need to define the binding from label to Plan.SelectedHotSpot again.
Thanks for your help.
I can't be sure that I have understood your problem exactly right because your question is somewhat unclear, but can you not just data bind to the Label.Content property in XAML? If you want to data bind the SelectedHotSpot.Name property of the Plan item that is currently selected in the ListBox, then you should be able to do something like this:
<Label Name="lblHotSpotName"
Content="{Binding SelectedItem.SelectedHotSpot.Name, ElementName=lstPlans}" />
UPDATE >>>
You're still better off using XAML for your Binding. Add a string property to bind to and then update that in your lstPlans_SelectionChanged handler instead:
<Label Name="lblHotSpotName" Content="{Binding SelectedItemHotSpotName}" />
...
private void lstPlans_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
canvas.Plan = PlanBLL.GetPlanByID(plans[lstPlans.SelectedIndex].ID);
SelectedItemHotSpotName = canvas.Plan.SelectedHotSpot.Name;
}
I'm not sure it will help or not, but in lstPlans_SelectionChanged try this Binding:
var myBinding = new Binding();
myBinding.Path = new PropertyPath("SelectedHotSpot.Name");
myBinding.Source = canvas.Plan;
lblHotSpotName.SetBinding(Label.ContentProperty, myBinding);
If SelectedHotSpot.Name doesn't change, when this line is not needed:
OnPropertyChanged("SelectedHotSpot.Name");
in SelectedHotSpot property declaration.
Don't see any issue in the given code (though, Raise property changed for .Name is not required).
I would suggest to confirm that selectedHotSpot always has some instance and is not null.
Try modifying your plan class and set:
selectedHotSpot = new HotSpot(Name="Default")
and you should see "Default" in your label.
Related
The app has a GridView in which each item is a color that the user can choose to customize the UI overriding the default SystemAccentColor (the one is defined by user on their system).
I managed to get the color of the item but even though I assign it as new value for SystemAccentColor I am not able to update the UI.
private void GridView_ItemClick(object sender, ItemClickEventArgs e)
{
// FIRST APROACH -----
GridViewItem gridViewItem = GVColors.ContainerFromItem(e.ClickedItem) as GridViewItem;
Ellipse ellipseItem = gridViewItem.FindDescendant<Ellipse>();
var theColor = (SolidColorBrush)ellipseItem.Fill;
Application.Current.Resources["SystemAccentColor"] = theColor;
// SECOND APPROACH ----
Windows.UI.Color theColor2 = new Windows.UI.Color
{
A = 1,
R = 176,
G = 37,
B = 37
};
var root = (FrameworkElement)Window.Current.Content;
root.Resources["SystemAccentColor"] = theColor2;
}
I'm currently reading this blog entry XAML Brewer, by Diederik Krols: Using a Dynamic System Accent Color in UWP but I want to know if the community knows another approach to change the accent color at runtime (or a method that I'm not aware of to Update/refresh the UI).
I assign it as new value for SystemAccentColor I am not able to update the UI.
Since you statically bind SystemAccentColor and it doesn't implement INotifyPropertyChanged interface, event though the value of SystemAccentColor changes, the UI which bound with it won't update directly.
Based on your requirement, you can add a class which implements INotifyPropertyChanged interface and add the SystemAccentColor as property in it. Then init the class instance in Application.Resources. After that, bind the UI with the SystemAccentColor property. For example, I create a class named SystemAccentColorSetting.
SystemAccentColorSetting.cs:
public class SystemAccentColorSetting : INotifyPropertyChanged
{
private SolidColorBrush systemAccentColor = new SolidColorBrush(Colors.Red);
public SolidColorBrush SystemAccentColor
{
get {
return systemAccentColor;
}
set {
systemAccentColor = value; OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
App.xaml:
<Application.Resources>
<ResourceDictionary>
<local:SystemAccentColorSetting x:Key="SystemAccentColorSetting"/>
</ResourceDictionary>
</Application.Resources>
Usage:
Assume that we bind the Background of Button with the SystemAccentColor property.
.xaml:
<Button x:Name="MyButton" Background="{Binding SystemAccentColor, Source={StaticResource SystemAccentColorSetting}}">hello</Button>
.cs:
If you want to change the value of Background, just change the SystemAccentColor property.
private void GridView_ItemClick(object sender, ItemClickEventArgs e)
{
GridViewItem gridViewItem = GVColors.ContainerFromItem(e.ClickedItem) as GridViewItem;
Ellipse ellipseItem = gridViewItem.FindDescendant<Ellipse>();
var theColor = (SolidColorBrush)ellipseItem.Fill;
((SystemAccentColorSetting)Application.Current.Resources["SystemAccentColorSetting"]).SystemAccentColor = theColor;
}
Starting from Win 10 1809 (build 17763), you can use the ColorPaletteResources Class.
By using it, you can change not only the Accent color at runtime, but also all the other system default colors for UI controls.
Due to a bug, you forst have to declare that ColorPaletteResources object into your App's XAML resources (with a key) and then you can use it at runtime.
Another bug is that, from what I have experimented some time ago, you can only change the Accent color for now, so you are lucky.
To see it in action, you can download Fluent XAML Theme Editor from the Windows Store or from GitHub.
Here is the link to the class itself and to some guidelines on how to use it.
Scenario
I have a custom combo box where i have a label in the Combobox selection box. I need to change the label as I noted in the second image. But I want to do it only when I select items by selecting the check box. I can select multiple items, so the label should be updated as a comma separated value of selected items. If there is not enough space to display the full text of the label there should be "..." symbol to indicate that there are more items selected in the combo box.
I created a custom Label by inheriting the text Box control where I do all the changes in the callback event of a Dependency property. (Check custom Text Box code)
Now the problem is that the callback event in the custom Text box control is not firing when I change the bounded property in the View model (I am doing this by adding values to the observable collection in the code behind in check box on Check event. Please Check check box event code).
I can see that first time when I load default data in the view model the line is hit by the break point in the "Getter" part of "SelectedFilterResources" . But I never get a hit in the Setter part of the property.
Custom Text Box
The custom text box has the "CaptionCollectionChanged" callback event. It is the place where I have all logic to achieve my scenario. "Resources item" here is a type of Model.
public class ResourceSelectionBoxLable : TextBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
IsReadOnly = true;
}
public static List<ResourceItem> LocalFilterdResources = new List<ResourceItem>();
#region Dependancy Properties
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
public ObservableCollection<ResourceItem> SelectedFilterdResources
{
get
{
return
(ObservableCollection<ResourceItem>) GetValue(FilterdResourcesProperty);
}
set
{
SetValue(FilterdResourcesProperty, value);
LocalFilterdResources = new List<ResourceItem>(SelectedFilterdResources);
}
}
#endregion
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var resourceSelectionBoxLable = d as ResourceSelectionBoxLable;
if (resourceSelectionBoxLable != null)
{
if (LocalFilterdResources.Count <= 0)
{
resourceSelectionBoxLable.Text = "Resources"
}
else
{
var actualwidthOflable = resourceSelectionBoxLable.ActualWidth;
var newValue = e.NewValue as string;
//Get the Wdith of the Text in Lable
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = resourceSelectionBoxLable.FontSize;
txtMeasure.Text = newValue;
double textwidth = txtMeasure.ActualWidth;
//True if Text reach the Limit
if (textwidth > actualwidthOflable)
{
var appendedString = string.Join(", ",
LocalFilterdResources.Select(item => item.ResourceCaption)
.ToArray());
resourceSelectionBoxLable.Text = appendedString;
}
else
{
if (LocalFilterdResources != null)
{
var morestring = string.Join(", ",
(LocalFilterdResources as IEnumerable<ResourceItem>).Select(item => item.ResourceCaption)
.ToArray());
var subsring = morestring.Substring(0, Convert.ToInt32(actualwidthOflable) - 4);
resourceSelectionBoxLable.Text = subsring + "...";
}
}
}
}
}
}
Custom Combo Box.
This is the control where I use the above custom label. This is also a custom control so most of the properties and styles in this controls are custom made. "DPItemSlectionBoxTemplate" is a dependency property where I enable the Selection Box of the combo box by adding an attached property to the control template. This control works fine, because I use this control in other places in my system for different purposes.
<styles:CommonMultiComboBox
x:Name="Resourcescmb" IsEnabled="{Binding IsResourceComboEnable,Mode=TwoWay}"
IsTabStop="False"
>
<styles:CommonMultiComboBox.ItemDataTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelect, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Click="CheckBox_Click"
Content="{Binding ResourceCaption}"
Style="{StaticResource CommonCheckBoxStyle}"
Tag ="{Binding}"
Checked="Resource_ToggleButton_OnChecked" />
</DataTemplate>
</styles:CommonMultiComboBox.ItemDataTemplate>
<styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
<DataTemplate>
<filtersTemplate:ResourceSelectionBoxLable
Padding="0"
Height="15"
FontSize="10"
SelectedFilterdResources="{Binding DataContext.FilterdResources,ElementName=root ,Mode=TwoWay}" />
</DataTemplate>
</styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
</styles:CommonMultiComboBox>
ViewModel
private ObservableCollection<ResourceItem> _resourceItems;
public ObservableCollection<ResourceItem> FilterdResources
{
get { return _resourceItems; }
set
{
SetOnChanged(value, ref _resourceItems, "FilterdResources");
}
}
Constructor of View Model
FilterdResources=new ObservableCollection<ResourceItem>();
"SetOnChanged" is a method in the View Model base class where we have the INotifyPropertichanged implementation.
Check Box Event
private void Resource_ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
var senderControl = sender as CheckBox;
if(senderControl==null)
return;
var selectedContent=senderControl.Tag as ResourceItem;
if (selectedContent != null)
{
ViewModel.FilterdResources.Add(selectedContent);
}
}
I can access the View Model from the Code behind through the View Model Property.
Why is the call back event not notified when I change bounded values? Am i missing something here? Dependency properties are supposed to work for two way bindings aren't they? Could any one please help me on this?
Thanks in advance.
Looks like your issue is that you're expecting the CaptionCollectionChanged event to fire when the bound collection is changed (i.e. items added or removed). When in fact this event will fire only when you're changing an instance of the bound object.
What you need to do here is to subscribe to ObservableCollection's CollectionChanged event in the setter or change callback (which you already have - CaptionCollectionChanged) of your dependency property.
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
var sender = d as ResourceSelectionBoxLable;
if (sender != null)
{
collection.CollectionChanged += sender.BoundItems_CollectionChanged;
}
}
}
private void BoundItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do your control logic here.
}
Don't forget to add cleanup logic - unsubscribe from collection change when collection instance is changed and so on.
I have window "ClientsWindow" and it's view model class "ClientsViewModel". In ViewModel i defined property "Clients" and bound it to DataGrid's itemssource property:
private ObservableCollection<tblClient> clients;
public ObservableCollection<tblClient> Clients
{
get { return clients; }
set
{
clients = value;
OnPropertyChanged("Clients");
}
}
In my window's constructor I set this property to new value by calling the method from wcf service like this:
Clients = new ObservableCollection<tblClient>(wcf.FilterClients(PageIndex, PageSize));
And it works perfect, I get 10 records from wcf service as it should be and the list is shown in datagrid. I insert some usercontrol which I want to use for datagrid pagination. It has ChangedIndexCommand defined like this:
ChangedIndexCommandProperty =
DependencyProperty.Register("ChangedIndexCommand", typeof(ICommand), typeof(GridPaging), new UIPropertyMetadata(null));
public ICommand ChangedIndexCommand
{
get { return (ICommand)GetValue(ChangedIndexCommandProperty); }
set { SetValue(ChangedIndexCommandProperty, value); }
}
I tried to bind command form my window's viewmodel to this command, so i did it this way:
private ICommand _cmdChangedIndex;
public ICommand cmdChangedIndex
{
get
{
if (_cmdChangedIndex == null)
{
_cmdChangedIndex = new DelegateCommand(delegate()
{
worker.DoWork += worker_FilterClientsList;
worker.RunWorkerCompleted += worker_FilterClientListCompleted;
worker.RunWorkerAsync();
});
}
return _cmdChangedIndex;
}
}
private void worker_FilterClientsList(object sender, DoWorkEventArgs e)
{
try
{
ServiceClient wcf = new ServiceClient();
Clients = new ObservableCollection<tblClient>(wcf.FilterClients(PageIndex, PageSize));
TotalCount = wcf.ReturnClientsCount();
}
catch (Exception ex)
{
}
}
private void worker_FilterClientListCompleted(object sender, RunWorkerCompletedEventArgs e)
{
worker.DoWork -= worker_FilterClientsList;
}
And here is the xaml:
<pc:GridPaging PageIndex="{Binding PageIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PageSize="{Binding PageSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TotalCount="{Binding TotalCount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" x:Name="clientsPagingControl"
ChangedIndexCommand="{Binding cmdChangedIndex, UpdateSourceTrigger=PropertyChanged}"
Visibility="Visible" VerticalAlignment="Top"
/>
So, while debugging everything works perfect! My command is fired when i click on the button of my userconrol, the method from wcf service is called properly and it returns new collection of items(count 2, as expected), my "Clients" property is set to new value BUT, UI still showing 10 items in my datagrid. I just cant figure out what is wrong?! Is this wrong way of binding commands to custom user controls?? Also let me note that, PageIndex, PageSize and TotalCount properties are of type int, and i bound them to my viewmodel properties, and they work perfect. But what is the problem with my command? I tried to be as clear as I could hope that you will understand what my problem is, and for any more info, please leave the comment.
OnPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
DataGrid binding:
<DataGrid IsReadOnly="True" Name="dgClients" AutoGenerateColumns="False" ItemsSource="{Binding Path=Clients, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
....
</DataGrid.Columns>
</DataGrid>
Just a thought, but it looks like you are using a BackgroundWorker class in your ICommand? In the worker_FilterClientsList method, you are setting the "Clients" observable collection property. I don't think you are able to manipulate the UI from within DoWork (it's running on a different thread). Try removing the try..catch block to see if it's hiding such an error.
You normally have to update the UI from the RunWorkerCompleted delegate (your worker_FilterClientListCompleted method).
Ok, so judging by your question, answers and the many comments, it would seem that your problem is un-reproducible. This means that you are on your own, as far as fixing your problem goes. However, this is not as bad as it sounds.
As there is no obvious problem with your displayed code, I cannot point out where your error lies. However, I can put you onto the right path to fix your own problem. It will take some time and effort on your part, but 'no pain... no gain', as they say.
One of the best ways that you find the problem in a complex project is to simplify it in a new, empty project. Normally when doing this, one of two things happens: either you find out what the problem was, or you create a concise working example that demonstrates your problem, which you can then post here (maybe as a new question, or instead of your current code). It's usually a win-win situation.
As it happens, the StackOverflow Help Center has a page to help with this. Please follow the advice in the How to create a Minimal, Complete, Tested and Readable example page to help you to simplify your problem.
One final point that I'd like to make is that normally in an application, the data access layer is separate from the UI. If you separate your different concerns like this, you will also find that it simplifies the situation further.
I'm new to WP8 and follow many tutorials. For parts of the menu I use a viewModel with NotifyPropertyChanged. When I get my list of news articles it creates a viewModel and displays it in a longListSelector.
But also I want to make 1 HubTile with the image and some preview-text of the first article. Is there a nice way to send some event to the .xaml.cs? Or do I have to make another viewModel for this one HubTile and make a binding?
Ony try was to make such a variable:
private bool _isDataLoaded = false;
public bool IsDataLoaded
{
get
{
return _isDataLoaded;
}
set
{
if (value != _isDataLoaded)
{
_isDataLoaded = value;
NotifyPropertyChanged("IsDataLoaded");
}
}
}
The same thing is used with "IsLoading"-variable to create a loading-indicator in the systemTray:
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("MainPage_Loaded-Funktion");
Binding binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsVisibleProperty, binding);
binding = new Binding("IsLoading") { Source = DataContext };
BindingOperations.SetBinding(
prog, ProgressIndicator.IsIndeterminateProperty, binding);
prog.Text = "Lade aktuelle Inhalte...";
}
Can I use this to call a function, when my variable is set and I get a notification?
The solution that helped me out was this:
<toolkit:HubTile Message="{Binding OnlineNews[0].TeaserText}"/>
Didn't know that you can access the viewModel like that. Thanks to Toni Petrina!
I would like to know every time a user modifies data in WPF DataGrid.
Is there a single event that I can use to do that? Or what is the minimal set of events that I can use to cover full set of data changes (Add row, delete row, modify row etc)?
I know that this is probably more than you are asking for, but once you do it, it's hard to go back. Whatever you are binding to ... some List, have that item implement IEditableObject.
that way you won't have to ever worry about whatever control/view implementation, events ets.
When the item is changed, the datagrid as well as plethora of .NET controls will set the IsDirty object to true.
These are not super great links but they will get you started thinking about maintaining isDirty flag.
https://msdn.microsoft.com/en-us/library/system.componentmodel.ieditableobject(v=vs.110).aspx
object editing and isDirty() flag
http://bltoolkit.net/doc/EditableObjects/EditableObject.htm
this is more what I am used to:
https://stackoverflow.com/a/805695/452941
Usually, when you are using MVVM, you bind the master list to an ObservableCollection and then the selected item to a specific instance. Inside your setters, you can raise events. This would be the most logical (read: the most common method I've seen) to capture updates / adds / deletes to a list of data.
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<DataGrid x:Name="dataGrid" AutoGeneratingColumn="OnAutoGeneratingColumn">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<EventSetter Event="Binding.SourceUpdated" Handler="OnDataGridCellSourceUpdated"/>
<EventSetter Event="Binding.TargetUpdated" Handler="OnDataGridCellTargetUpdated"/>
</Style>
</DataGrid.Resources>
</DataGrid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.dataGrid.ItemsSource = new ObservableCollection<Person>()
{
new Person() { Name = "John", Surname = "Doe" },
new Person() { Name = "Jane", Surname = "Doe" }
};
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dataGridBoundColumn = e.Column as DataGridBoundColumn;
if (dataGridBoundColumn != null)
{
var binding = dataGridBoundColumn.Binding as Binding;
if (binding != null)
{
binding.NotifyOnSourceUpdated = true;
binding.NotifyOnTargetUpdated = true;
}
}
}
private void OnDataGridCellSourceUpdated(object sender, DataTransferEventArgs e)
{
this.OnDataGridCellChanged((DataGridCell)sender);
}
private void OnDataGridCellTargetUpdated(object sender, DataTransferEventArgs e)
{
this.OnDataGridCellChanged((DataGridCell)sender);
}
private void OnDataGridCellChanged(DataGridCell dataGridCell)
{
// DataContext is MS.Internal.NamedObject for NewItemPlaceholder row.
var person = dataGridCell.DataContext as Person;
if (person != null)
{
var propertyName = ((Binding)((DataGridBoundColumn)dataGridCell.Column).Binding).Path.Path;
var propertyValue = TypeDescriptor.GetProperties(person)[propertyName].GetValue(person);
// TODO: do some logic here.
}
}
}
This is what I used for some complex DataGridCell formatting based on a Person (just some POCO) instance, property name and property value.
But if you want to be able to know when to save the data and you use MVVM, then the best way to do this would be to have original value and current value for every editable property in your view model / model. When data is loaded, original and current value would be equal, and if property is changed through DataGrid or any other way, only current value is updated. When data needs to be saved, just check if any item has any property that has different original and current value. If the answer is yes, then data should be saved, because it has been changed since last load / save, otherwise data is same as when loaded, so no new saving is required. Also, when saving, current values must be copied to original values, because data is again equal to the saved data, like when it was last loaded.
if you use mvvm you do not need to know when the "user modify data in the data grid" you have to know when the underlying collection change.
so if you use datatable(HasChanges/RejectChanges...) you have that all built in. if you use poco collections then your items at least have to implement INotifyPropertyChanged - if its raised the user modify data. Maybe IEditable is a good one too for reject changes and so on.