Making a Windows 8: Universal Application, I have the following question:
Why the Height of each control in MyGridView.Items does not change after the new value is assigned to height in MyGridView_SelectionChanged method? Within the OnNavigatedTo method it sets the Height value correctly...
public sealed partial class NewPage : Page
{
public double height;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
foreach (var card in e.Parameter as List<MyClass>)
{
MyControl myControl = new MyControl(card);
BindingOperations.SetBinding(myControl, HeightProperty, new Binding { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, Mode = BindingMode.TwoWay, Source = this, Path = new PropertyPath("height") });
MyGridView.Items.Add(myControl);
}
}
private void MyGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
height = 100;
}
}
I also tried with the DependencyProperty:
public static readonly DependencyProperty MyGridViewItemHeightProperty = DependencyProperty.Register("MyGridViewItemHeight", typeof(double), typeof(GridViewItem), null);
public double MyGridViewItemHeight
{
set { SetValue(MyGridViewItemHeightProperty, value); }
get { return (double)GetValue(MyGridViewItemHeightProperty); }
}
and
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
foreach (var card in e.Parameter as List<MyClass>)
{
MyControl myControl = new MyControl(card);
BindingOperations.SetBinding(myControl, HeightProperty, new Binding { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, Mode = BindingMode.TwoWay, Source = this, Path = new PropertyPath("MyGridViewItemHeightProperty") });
}
}
but it gave the same result...
Related
I am using WPF Live Charts Library. I am trying to create a bar chart generically.
Following is my code. Can't do it in XAML as it doesn't support generics.
public abstract class AbstractGenericBarChart<T> : UserControl, INotifyPropertyChanged
{
SeriesCollection _SeriesCollection;
public SeriesCollection SeriesCollection { get { return _SeriesCollection; } set { _SeriesCollection = value; notifyPropertyChanged("SeriesCollection"); } }
string[] _Labels;
public string[] Labels { get { return _Labels; } set { _Labels = value; notifyPropertyChanged("Labels"); } }
Func<int, string> _Formatter;
public Func<int, string> Formatter { get { return _Formatter; } set { _Formatter = value; notifyPropertyChanged("Formatter"); } }
public abstract void constructChart(List<T> chartItems);
public void init(string xLabel, string yLabel)
{
renderChart(xLabel, yLabel);
}
public void renderChart(string xLabel, string yLabel)
{
CartesianChart chart = new CartesianChart { Margin = new Thickness(10, 10, 10, 10), LegendLocation = LegendLocation.Bottom, DataTooltip = new DefaultTooltip { SelectionMode = TooltipSelectionMode.SharedYValues } };
Axis xAxis = new Axis { Foreground = Brushes.Black, FontSize = 14d, Title = xLabel };
Axis yAxis = new Axis { Foreground = Brushes.Black, FontSize = 14d, Title = yLabel };
chart.AxisX.Add(xAxis);
chart.AxisY.Add(yAxis);
setBinding("LabelFormatter", Formatter, xAxis, Axis.LabelFormatterProperty);
setBinding("Labels", Labels, yAxis, Axis.LabelsProperty);
setBinding("Series", SeriesCollection, chart, CartesianChart.SeriesProperty);
Content = chart;
}
public void setBinding(string propertyName, object source, FrameworkElement control, DependencyProperty dependencyProperty)
{
Binding binding = new Binding(propertyName)
{
Source = source
};
control.SetBinding(dependencyProperty, binding);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void notifyPropertyChanged(string prop)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
internal class BarChart : AbstractGenericBarChart<TopTransactingCount>
{
public override void constructChart(List<TopTransactingCount> chartItems)
{
SeriesCollection = new SeriesCollection
{
new RowSeries
{
Title = "Transaction Count",
Values = new ChartValues<long>(chartItems.Select(x=>x.TransCount))
}
};
Labels = chartItems.Select(x => x.Date.ToShortDateString()).ToArray();
Formatter = value => value.ToString();
DataContext = this;
}
}
At startup I see an empty chart control which is expected.
I call constructChart method on submit button click.
public partial class TotalTransCountsChart : UserControl, IChart
{
private BarChart chart = new BarChart();
List<object> chartData;
public TotalTransCountsChart()
{
InitializeComponent();
}
public void init(List<object> chartData)
{
this.chartData = chartData;
chart.init("Transaction Count", "Date");
chart.constructChart(chartData.Cast<TopTransactingCount>().ToList());
grid.Children.Add(chart);
Grid.SetRow(chart, 3);
}
private void CmdSubmit_Click(object sender, System.Windows.RoutedEventArgs e)
{
chart.constructChart(chartData.Cast<TopTransactingCount>().ToList());
}
}
However, the chart still remains empty. I think the binding part in the code is not working as expected. I am stuck at this point.
it looks like you are creating Bindings incorrectly (try to confirm it from Visual Studio Output window - it reports messages about incorrect bindings).
for example:
setBinding("Labels", Labels, yAxis, Axis.LabelsProperty);
public void setBinding(string propertyName, object source, FrameworkElement control, DependencyProperty dependencyProperty)
{
Binding binding = new Binding(propertyName)
{
Source = source
};
control.SetBinding(dependencyProperty, binding);
}
Labels is a string[], and you have a binding , which attempts to use "Labels" property - which doesn't exist.
you need a valid binding source, most likely DataContext:
setBinding("LabelFormatter", DataContext, xAxis, Axis.LabelFormatterProperty);
setBinding("Labels", DataContext, yAxis, Axis.LabelsProperty);
setBinding("Series", DataContext, chart, CartesianChart.SeriesProperty);
or better yet - don't specify source and all bindings will connect to current DataContext, even if it updates:
public void setBinding(string propertyName, object source, FrameworkElement control, DependencyProperty dependencyProperty)
{
Binding binding = new Binding(propertyName);
control.SetBinding(dependencyProperty, binding);
}
How can I get the selected item of my custom picker? It always goes back to the initial state "Sonstige" (= "Other"). So the value is not changeable.
I'm facing the problem for hours but have no further idea on how to fix it. I'm new to custom rendering.
I would like to use the DoneBtn_Clicked function to set the new value to the picker:
void DoneBtn_Clicked(object sender, EventArgs e){}
Screenshot of the custom picker:
My code:
Class PickerRendererIos.cs :
[assembly: ExportRenderer(typeof(MyPickerRenderer), typeof(PickerRendererIos))]
namespace DigitalNatives.iOS
{
public class PickerRendererIos : PickerRenderer, IUIPickerViewDelegate
{
IElementController ElementController => Element as IElementController;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
UIPickerView pickerView = (UIPickerView)Control.InputView;
pickerView.WeakDelegate = this;
pickerView.BackgroundColor = UIColor.White; //set the background color of pickerview
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
Control.Layer.BorderWidth = 1;
Control.BorderStyle = UITextBorderStyle.Line;
Control.TextColor = UIColor.Blue;
}
[Export("pickerView:viewForRow:forComponent:reusingView:")]
public UIView GetView(UIPickerView pickerView, nint row, nint component, UIView view)
{
UILabel label = new UILabel
{
TextColor = UIColor.Blue,
Text = Element.Items[(int)row].ToString(),
TextAlignment = UITextAlignment.Center,
};
var picker = this.Element;
return label;
}
}
}
Cause:
Because you set the delegate of the pickerview pickerView.WeakDelegate = this;.But you did not implement all method in the delegate.
Solution:
You can override the click action of the button Done.Refer the following code
in the Custom Renderer
[assembly: ExportRenderer(typeof(MyPicker), typeof(MyPickerRenderer))]
namespace xxx.iOS
{
public class MyPickerRenderer:PickerRenderer,IUIPickerViewDelegate,IUIPickerViewDataSource
{
string SelectedValue;
public MyPickerRenderer()
{
}
public nint GetComponentCount(UIPickerView pickerView)
{
return 1;
}
public nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
return Element.Items.Count;
}
[Export("pickerView:viewForRow:forComponent:reusingView:")]
public UIView GetView(UIPickerView pickerView, nint row, nint component, UIView view)
{
UILabel label = new UILabel
{
TextColor = UIColor.Blue,
Text = Element.Items[(int)row].ToString(),
TextAlignment = UITextAlignment.Center,
};
var picker = this.Element;
return label;
}
[Export("pickerView:didSelectRow:inComponent:")]
public void Selected(UIPickerView pickerView, nint row, nint component)
{
Control.Text = Element.Items[(int)row];
SelectedValue= Element.Items[(int)row];
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
SelectedValue = Element.Items[0];
UIPickerView pickerView = (UIPickerView)Control.InputView;
pickerView.WeakDelegate = this;
pickerView.DataSource = this;
// get the button Done and rewrite the event
UIToolbar toolbar = (UIToolbar)Control.InputAccessoryView;
UIBarButtonItem done = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, (object sender, EventArgs click) =>
{
Control.Text = SelectedValue;
toolbar.RemoveFromSuperview();
pickerView.RemoveFromSuperview();
Control.ResignFirstResponder();
});
UIBarButtonItem empty = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace, null);
toolbar.Items = new UIBarButtonItem[] { empty,done };
}
}
}
}
When I call Generate() the events associated with the ObservableCollection (X) is not fired.
What am I doing wrong?
The code:
MyControl.xaml.cs
public ObservableCollection<double> X
{
get { return (ObservableCollection<double>)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public static readonly DependencyProperty XProperty =
DependencyProperty.Register(
"X", typeof(ObservableCollection<double>),
typeof(MyControl),
new PropertyMetadata(
new ObservableCollection<double>(),
new PropertyChangedCallback(OnXChanged)));
private static void OnXChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as MyControl;
// DoSomething
}
A XAML random page that uses MyControl:
<local:MyControl
Title="Test"
X="{Binding TestX, Mode=TwoWay}"
/>
That page's .cs
public sealed partial class MainPage : Page
{
public ObservableCollection<double> TestX { get; set; }
private static Random rand_ = new Random();
public MainPage()
{
this.InitializeComponent();
TestX = new ObservableCollection<double>();
}
private void Generate()
{
TestX.Clear();
for (int i = 0; i < 100; ++i)
{
TestX.Add(rand_.Next(1, 100));
}
}
....
}
Please note that I don't see any BindingExpression error in the output window.
Update
I noticed that if do like this in the page, it works:
TestX = new ObservableCollection<double>();
this.MyUserControlInstance.X = TestX;
You're missing two things there:
First:
Make sure you set the DataContext in your constructor:
public MainPage()
{
this.InitializeComponent();
DataContext = this; // Important, but you should use a seperated ViewModel instead
TestX = new ObservableCollection<double>();
}
Second:
Your class is missing the INotifyPropertyChanged implementation, as well as the PropertyChanged call for your TestX property:
private ObservableCollection<double> _testX;
public ObservableCollection<double> TestX
{
get { return _testX; }
set
{
if (value == _testX) return;
_testX = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
Side note: Do MVVM!
I named this question of mine Part II since I've already asked a similar but simpler question about creating an ItemTemplat for ListBox in WPF in here Create ItemTemplate for ListBox in code-beind in WPF
Now I'm going to expand my question. I want to have an ItemTemplate for a ListBox so that it can be used either with or without binding to an ObservableCollection.
If I don't want to bind the ItemsSource to an ObservableCollection I use the code as follows:
var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
textBlockFactory.SetValue(TextBlock.TextProperty, new Binding(".")); // Here
textBlockFactory.SetValue(TextBlock.BackgroundProperty, Brushes.Red);
textBlockFactory.SetValue(TextBlock.ForegroundProperty, Brushes.Wheat);
textBlockFactory.SetValue(TextBlock.FontSizeProperty, 18.0);
var template = new DataTemplate();
template.VisualTree = textBlockFactory;
MyListBox.ItemTemplate = template;
But it doesn't work for ItemsSource binding to an ObservableCollection since the TextBlock.TextProperty must binds to the DisplayMemberPath property.
Sorry for bad grammar.
First of all you need to create a variable that will determine the state: are using a collection, or just an array of strings. This flag can also be a dependency property, in my example it's a SomeFlag:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
bool SomeFlag = false;
if (SomeFlag == false)
{
var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
textBlockFactory.SetValue(TextBlock.TextProperty, new Binding("."));
textBlockFactory.SetValue(TextBlock.BackgroundProperty, Brushes.Red);
textBlockFactory.SetValue(TextBlock.ForegroundProperty, Brushes.Wheat);
textBlockFactory.SetValue(TextBlock.FontSizeProperty, 18.0);
var template = new DataTemplate();
template.VisualTree = textBlockFactory;
MyListBox.ItemTemplate = template;
}
else
{
MyListBox.DisplayMemberPath = "Name";
MyListBox.SelectedValuePath = "Age";
}
}
And for testing, add this handler of SelectionChanged event:
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("SelectedValue is " + MyListBox.SelectedValue);
}
Below is a full example:
XAML
<Grid>
<ListBox Name="MyListBox"
SelectionChanged="MyListBox_SelectionChanged"
ItemsSource="{Binding Path=MyCollection}" />
</Grid>
Code-behind
public partial class MainWindow : Window
{
ViewModel MyViewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = MyViewModel;
MyViewModel.MyCollection = new ObservableCollection<Person>();
MyViewModel.MyCollection.Add(new Person()
{
Age = 22,
Name = "Nick",
});
MyViewModel.MyCollection.Add(new Person()
{
Age = 11,
Name = "Sam",
});
MyViewModel.MyCollection.Add(new Person()
{
Name = "Kate",
Age = 15,
});
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
bool SomeFlag = false;
if (SomeFlag == false)
{
var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
textBlockFactory.SetValue(TextBlock.TextProperty, new Binding("."));
textBlockFactory.SetValue(TextBlock.BackgroundProperty, Brushes.Red);
textBlockFactory.SetValue(TextBlock.ForegroundProperty, Brushes.Wheat);
textBlockFactory.SetValue(TextBlock.FontSizeProperty, 18.0);
var template = new DataTemplate();
template.VisualTree = textBlockFactory;
MyListBox.ItemTemplate = template;
}
else
{
MyListBox.DisplayMemberPath = "Name";
MyListBox.SelectedValuePath = "Age";
}
}
private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("SelectedValue is " + MyListBox.SelectedValue);
}
}
public class ViewModel : NotificationObject
{
#region MyCollection
public ObservableCollection<Person> MyCollection
{
get;
set;
}
#endregion
}
#region Model
public class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
#endregion
#region NotificationObject
public class NotificationObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
Property grid do not show new value of selected object.
For example:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.Refresh();
The property in property grid is still "1";
It is changing only if you click on this property.
Or like that:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.SelectedObject = o;
but in this case focus will be changed to PropertyGrid.
As I told you in my comment, your code is not enough to understand your issue. Presented like this it should work. Here is mine that works well:
public class Target
{
private int _myInt = 1;
public int MyInt { set; get; }
public static Target target = new Target();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Button button = new Button()
{
Text = "Click me",
Dock = DockStyle.Bottom
};
Form form = new Form
{
Controls = {
new PropertyGrid {
SelectedObject = Target.target,
Dock = DockStyle.Fill,
},
button
}
};
button.Click += new EventHandler(button_Click);
Application.Run(form);
}
static void button_Click(object sender, EventArgs e)
{
Target.target.MyInt = 2;
Form form = Form.ActiveForm;
(form.Controls[0] as PropertyGrid).Refresh();
}
}
The call to Refresh() actually rebuilds the grid. Remove it and you will see the change only when you click the property.
Cause you just not gave some code, here is a working example.
Just put a Button and a PropertyGrid onto a form.
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class FormMain : Form
{
Random rand;
MyObject obj;
public FormMain()
{
InitializeComponent();
rand = new Random();
obj = new MyObject();
propertyGrid1.SelectedObject = obj;
}
private void button1_Click(object sender, EventArgs e)
{
obj.MyValue = rand.Next();
obj.IsEnabled = !obj.IsEnabled;
obj.MyText = DateTime.Now.ToString();
propertyGrid1.Refresh();
}
}
public class MyObject : INotifyPropertyChanged
{
private int _MyValue;
public int MyValue
{
get
{
return _MyValue;
}
set
{
_MyValue = value;
NotifyPropertyChanged("MyValue");
}
}
private string _MyText;
public string MyText
{
get
{
return _MyText;
}
set
{
_MyText = value;
NotifyPropertyChanged("MyText");
}
}
private bool _IsEnabled;
public bool IsEnabled
{
get
{
return _IsEnabled;
}
set
{
_IsEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}