Issue when adding user control in data grid using wpf programatically - wpf

I have created user control and adding to data grid using code behind . For user control i have created 2 constructor to pass the data for showing .
User control constructor:
public OverlayControlView()
{
InitializeComponent();
}
public OverlayControlView(string value, List<Function> functionList)
{
InitializeComponent();
OverlayValue = value;
OverlayMenuItem = functionList;
}
Adding user control to data grid:
var overlayControlView = new OverlayControlView(caption, functionList);
FrameworkElementFactory factory = new FrameworkElementFactory(overlayControlView.GetType());
var dataTemplate = new DataTemplate(typeof(DependencyObject));
dataTemplate.VisualTree = factory;
dataGridTemplateColumn.CellTemplate = dataTemplate;
dataGrid.Columns.Add(dataGridTemplateColumn);
Problem is after adding user control to grid , its always calling parameter less constructor and the value inside user control become empty. It call parameter constructor first when i am adding user control programmatically then it automatically calling parameter less constructor.
How to solve this issue!

Related

Retrieving Property from another WPF form

I am a beginner in WPF and trying to learn,So Sorry for this type of query.
I have added a property named GetName in WPF Form1 as below:
public string GetName
{
get { return this.uname.Text; }
set { this.uname.Text = value; }
}
Trying to retrieve the GetName property in WPF Form2 as below:
public Home()
{
InitializeComponent();
Form1 mn = new Form1 ();
MessageBox.Show(mn.GetName.ToString());
}
But I am getting prompt with blank. What I am doing wrong here. Please help.
You create a new Form:
Form1 mn = new Form1 ();
Then you request the Text value of what I assume is a TextBox control to be shown in a MessageBox:
MessageBox.Show(mn.GetName.ToString());
I would not expect GetNameto return a value, as when a TextBox control is created, unless specified, it will not have a value.
Between creating the Form and showing the MessageBox, you do not show your Form, for example:
mn.Show();
You need to show your Form first, input a value into your uname control and then request its value.
Calling ToString() on GetName is redundant, just use:
MessageBox.Show(mn.GetName);

How can I update the source of a binding when cancelling update in DependencyProperty's CoerceValueCallback?

I have a control (named GridView in the example below) and a view model that are bound through 2-way binding on their SelectedValue property. I want to forbid certain values for that property in the GridView control through the use of a CoerceValueCallback.
When the binding pushes that invalid value (42 in this example) into the DependencyProperty, the CoerceValue method discards that value and the control keeps its previous value as expected. Unfortunately, because the DependencyProperty has not changed the binding does not update the source value in the view model.
How can I update the property in the view model with the value in the control when cancelling the property value change?
class ViewModel : INotifyPropertyChanged
{
public int? SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
RaisePropertyChanged("SelectedValue");
}
}
private int? _selectedValue;
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
class GridView : FrameworkElement
{
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(GridView),
new PropertyMetadata(null, null, new CoerceValueCallback(CoerceValue)));
private static object CoerceValue(DependencyObject d, object baseValue)
{
// forbid value 42 and return previous value
if (Equals(baseValue, 42))
{
return d.GetValue(GridView.SelectedValueProperty);
}
return baseValue;
}
}
[STAThread]
static void Main()
{
ViewModel vm = new ViewModel();
GridView grid = new GridView();
Binding binding = new Binding("SelectedValue") { Source = vm, Mode = BindingMode.TwoWay };
grid.SetBinding(GridView.SelectedValueProperty, binding);
vm.SelectedValue = 12;
Console.WriteLine("should be 12: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
grid.SelectedValue = 23;
Console.WriteLine("should be 23: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
vm.SelectedValue = 42;
Console.WriteLine("should still be 23: {0} = {1}", grid.SelectedValue, vm.SelectedValue);
}
I have tried
var binding = BindingOperations.GetBindingExpression(d,
GridView.SelectedValueProperty);
binding.UpdateSource();
in the CoerceValue method to no avail. I even tried the previous 2 lines with Dispatcher.CurrentDispatcher.BeginInvoke(updateSource, DispatcherPriority.DataBind); which was suggested in a similar stack overflow question.
Any other ideas?
Update:
#Berryl suggested I should do that logic in the view model and I agree with him... except that I can't. I will explain the full use case I tried to simplify in the code above.
GridView is not my control but a 3rd-party data grid from which I inherit. When a user edits a record, we pop up a window in which the user edits the record and then the view model refreshes the data and reselects the old record using its ID. Everything works well until the user uses the filtering capabilities of the grid. If the edit on the record would make the row hidden because of the filter, the following occurs:
View model sets in ValueList property
The grid reads that new list
The grid filters the list
View Model sets the SelectedValue property with a record that is in the ValueList
The grid reads that new SelectedValue but discards it because it is filtered out
Grid has SelectedValue = 23 and View Model has SelectedValue = 42
User double-clicks on the selected row (he sees 23) to edit the record
The ICommand mapped to the double-click is called in the view model
View model launches edit window with record from its SelectedValue property
The user edits record 42 but thinks it is 23.
I found this solution in my emails from way back and thought I should share it (with a little cleanup). This needed support calls to both Telerik and Microsoft at the time.
Basically, the reason I was not able to cancel a property update from the CoerceValueCallback is because:
the binding is updated before the value is coerced in the logic for the dependency property
this behaviour of CoerceValueCallback is considered by design
Now the long answer on how to fix it:
static GridView()
{
SelectedItemProperty.OverrideMetadata(typeof(GridView), new FrameworkPropertyMetadata(null, CoerceSelectedItemProperty));
}
// Rewriting SelectedItem coercion logic because of the following issue (we had support calls to both Telerik and Microsoft)
// When
// - a filter is applied on the grid
// - user refreshes after modifying a record
// - the record he has changed would be filtered out by the the grid filter
// - the view model sets the SelectedItem to the modified record (which is hidden now)
//
// Telerik's coercion method resets the selected item to a visible row but because WPF's
// binding occurs before the coercion method, the view model is not updated.
// Even a call to UpdateSource() on the binding does not work in this case because the binding
// is already updating the target while this happens so it does nothing when you call it.
private static object CoerceSelectedItemProperty(DependencyObject d, object baseValue)
{
// Call normal Coercion method because we don't want to rewrite Telerik's logic
// and keep result to return it at the end.
object returnValue = SelectedItemProperty.GetMetadata(typeof(RadGridView)).CoerceValueCallback(d, baseValue);
var control = (GridView)d;
// If coerce returned something other than DependencyProperty.UnsetValue we can use it to push it back to
// the binding source because it is of the right type and the right value.
// The only case when we can use control.SelectedItem is when coerce returned UnsetValue otherwise the
// view model is always one click late.
object officialValue = returnValue == DependencyProperty.UnsetValue
? control.SelectedItem
: returnValue;
var binding = control.GetBindingExpression(SelectedItemProperty);
var source = binding.ResolvedSource;
var property = source.GetType().GetProperty(binding.ResolvedSourcePropertyName);
property.SetValue(source, officialValue, null);
return returnValue;
}

Passing data to user control in MVVM pattern

Background:
I have WPF application with a main window containing a user control. I want to pass a value from the main window to the user control.
In the Main window's constructor I have code:
public MainWindow()
{
InitializeComponent();
_vm = new MainWindowViewModel();
this.DataContext = _vm;
ucControl = new UserControl1("NameSet");
}
(ucControl is my user control)
User control has two constructors:
public UserControl1()
{
InitializeComponent();
this.ID = ID.GetNewID;
}
public UserControl1(string name)
{
InitializeComponent();
_vm = new UCViewModel(name);
this.DataContext = _vm;
this.ID = ID.GetNewID;
}
The problem is: although the second constructor (with parameter) is called, it is not loaded in the main window. I checked the ID (this.ID) in the user control's loaded event and I see the ID set in the default constructor and its DataContext is null. Because of this reason, I do not get the "name" string in my user control.
Any help please? Since I am using MVVM pattern I do not want to expose properties in user control (view) to be set from main window just for this.
You are instantiating the UserControl1 object twice:
Once within the XAML. The <uc:UserControl1> element instantiates a UserControl1 object, using the default constructor, and assigns it to the member ucControl.
You instantiate it again within the constructor of the MainWindow object
If you put a break point in the constructor of UserControl, you'll notice it is called twice. I assume WPF instantiate and initialize the XAML's UserControl (#1 from above) after you assign the dynamic UserControl (#2 from above), and this is why you see the former in the logical tree of MainWindow.
You should have only one instance. If you want to parameterized a user control, the canonical paradigm is what you mention that you don't want to do (why??). If you had such a property, you could set it in the XAML: <uc:UserControl1 x:Name="..." YourProperty="NameSet>
exposing such a property is a single line in the UserControl:
public YourProperty { get; set; }
If you insist of not having this line, you should do the following:
Remove the XAML's user control.
In main window, subscribe to the Loaded event
In the handler of the Loaded event, instantiate a new UserControl1 - with whatever constructor parameter that you want.
Manually add it to the Children array of the parent Grid element
Clearly this isn't my recommendation. In addition to the complexity, with the former method you'll also work very well with the Visual Studio designer.

How to databind two dependency properties belonging to two different controls?

Alternatively: How to subscribe to the PropertyChanged event defined by INotifyPropertyChanged thru the databinding of two dependency properties?
I have two separate user controls inside my main window. One control contains the parameters that affect the other control, let’s call it the display control. I want the parameter control to act as the datasource of the display control so that when I change a parameter in the parameter control, the display control be listening and reacts accordingly.
For this I created a class that implements INotifyPropertyChanged that stores these parameters and created dependencies properties of this class type in both controls. I was expecting that if I binded one control property to the other I would get the desired behaviour, but unfortunately I am missing something important because the display control is not reacting.
On a closer inspection with the debugger, I notice that my event PropertyChangedEventHandler PropertyChanged was always null when a property had changed, and everything I have read indicates, that no one is listening.
Because the display control is created in real time, I have to create the binding programmatically like this:
var DispayControlValuesBinding = new Binding();
DispayControlValuesBinding.Source = DisplayControlsControl;
DispayControlValuesBinding.Path = new PropertyPath("DisplayControlValues");
DispayControlValuesBinding.Mode = BindingMode.OneWay;
DispayControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
DispayControlValuesBinding.NotifyOnSourceUpdated = true;
//
graph.SetBinding(Graph.DisplayControlValuesProperty, DisplayControlValuesBinding);
Both controls have a dependency property called DispayControlValues. I try to bind the DisplayControlControl's DisplayControlValues property to the graph control's DisplayControlValues property.
When the application runs, it initializes the parameter control, then with a user request a display control is created programmatically and the binding is made. Then I change a value in the parameter control, this is catch by the parameters class that implements the INotifyPropertyChanged interface but because no one is listening, the event handler is null and here is where I am stuck.
Your help is greatly appreciated!
Here are more details as requested:
I have one user control that exposes the parameters that changes the behaviour of another control. This control has a dependency property that contains parameter details and implements the INotifyPropertyChanged interface.
Here is the class:
public class ZoomGraphControlValues : INotifyPropertyChanged
{
private bool _displayRaw;
public bool DisplayRaw
{
get { return _displayRaw; }
set
{
_displayRaw = value;
OnPropertyChanged(new PropertyChangedEventArgs("DisplayRaw"));
}
}
private bool _enableFit;
public bool EnableFit
{
get { return _enableFit; }
set
{
_enableFit = value;
OnPropertyChanged(new PropertyChangedEventArgs("EnableFit"));
}
}
public ZoomGraphControlValues()
{}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Here is the dependency property:
public ZoomGraphControlValues ControlValues
{
get { return (ZoomGraphControlValues)GetValue(ControlValuesProperty); }
set { SetValue(ControlValuesProperty, value); }
}
public static readonly DependencyProperty ControlValuesProperty =
DependencyProperty.Register("ControlValues", typeof(ZoomGraphControlValues), typeof(ZoomGraphControls), new PropertyMetadata(null, OnControlValuesPropertyChanged));
private static void OnControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as ZoomGraphControls;
myObj.OnControlValuesPropertyChanged(e);
}
private void OnControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ControlValues != null)
{
IniValues();
}
}
Then I have the display user control. This control also implements a dependency property of the same type as the other control and I want this control to be the target of the binding, so that when I change values in the parameter control, this control reflect the changes.
Here is the dependency property of this control:
public ZoomGraphControlValues ZoomGraphControlValues
{
get { return (ZoomGraphControlValues)GetValue(ZoomGraphControlValuesProperty); }
set { SetValue(ZoomGraphControlValuesProperty, value); }
}
public static readonly DependencyProperty ZoomGraphControlValuesProperty =
DependencyProperty.Register("ZoomGraphControlValues", typeof(ZoomGraphControlValues), typeof(zoomGraph), new PropertyMetadata(null, OnZoomGraphControlValuesPropertyChanged));
private static void OnZoomGraphControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as zoomGraph;
myObj.OnZoomGraphControlValuesPropertyChanged(e);
}
private void OnZoomGraphControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ZoomGraphControlValues != null)
{
// update the control with the new parameters
ShowRawData(ZoomGraphControlValues.DisplayRaw);
SetChartBehabiour();
}
}
The Parameters control is initialized since the beginning of the application cycle. The display control gets created as per user request into a tab, so I have to create the control programmatically and thereby the binding as well:
//create the tab and wire tab events
//…
//create a display control
var graph = new zoomGraph();
// initialize the parameters class
var zgcv = new ZoomGraphControlValues
{
DisplayRaw = true,
ChartBehaviour = ChartBehaviour.Zoom
};
//assign the parameters class to the parameters user control dependency property
ZoomGraphControlsControl.ControlValues = zgcv;
//create the binding of the parameter control to the display control by linking their respective dependency properties
var zoomGraphControlValuesBinding = new Binding();
zoomGraphControlValuesBinding.Source = ZoomGraphControlsControl;
zoomGraphControlValuesBinding.Path = new PropertyPath("ControlValues");
zoomGraphControlValuesBinding.Mode = BindingMode.TwoWay;
zoomGraphControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
zoomGraphControlValuesBinding.NotifyOnSourceUpdated = true;
zoomGraphControlValuesBinding.NotifyOnTargetUpdated = true;
graph.SetBinding(zoomGraph.ZoomGraphControlValuesProperty, zoomGraphControlValuesBinding);
//…
// add the user control to a tab
When I change a parameter in the parameter control I can see that it tries to fire the OnPropertyChanged event but it is always null. Because of this I think I am lacking something.
You are setting the binding mode to "OneWay" which means the view model will never get updated when the value changes in the view. Change the Binding mode to "TwoWay" and try again.
Also, check if you are changing the complete instance of "DisplayControlValues" or just properties on that class, because your binding is only set to fire when the entire instance changes, not its properties.
In addition to that, keep in mind that you can bind properties of two different controls using the Binding.ElementName property, which would make it unnecessary for you to create a view model, unless there is anything in the code behind you need to do when these values change.
If you post more code and XAML it will be easier to find the most appropriate way to solve your issue.

WPF DataGrid ItemSource Refresh - GridColumns shows strange behaviour

I am developing WPF UserControl based on WPF DataGrid, to support dynamic column generation with our own business based context menu.
I've created Dependency Property called DataSource, when I set DataSource calling a custom method to Bind my dataSource to Create columns on the fly and set ItemSource property. All works fine the first time. I have a context menu called Refresh, while the user clicks Refresh the SQL will execute and the same cycle of the above-mentioned actions will happen. During the second time, the rows and columns are created perfectly. But when I do Horizontal scroll the Column headers are NOT showing properly, it loses their visual state while scrolling.
My Custom Property - DataSource
public static DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(GridDataModel), typeof(MyGridView),
new PropertyMetadata((dependencyObject, eventArgs) =>
{
if (eventArgs.OldValue != null)
{
((GridDataModel)eventArgs.OldValue).Dispose();
}
BindToDataSource((MyGridView)dependencyObject, (GridDataModel)eventArgs.NewValue);
}));
My Custom method which is calling everytime I set DataSource property:
private static void BindToDataSource(MyGridView view, GridDataModel dataModel)
{
if (view.ViewModel != null)
{
BindingOperations.ClearAllBindings(view.GridView);
view.GridView.Items.Clear();
view.GridView.Columns.Clear();
view.GridView.ItemsSource = null;
view.ViewModel.Dispose();
}
view.ViewModel = new MyGridViewModel(dataModel);
view.ViewModel.PrepareGridView();
view.LayoutRoot.DataContext = view.ViewModel;
view.CreateColumns();
view.GridView.SetBinding(DataGrid.ItemsSourceProperty, new Binding("DisplayRows"));
}
The Below code I used to call on Refresh Menu Click:
private void OnRefreshClick(object sender, RoutedEventArgs e)
{
var data = new TestDataAccess();
DataSource = data.MakeGridModel("select Top 200 * from ApplicationUSer"); //Assigning DataSource Again, which will call the above method.
GridView.UpdateLayout();
}
After refresh, you could see the column alignment goes strange when doing the horizontal scroll.
Tried using GridColumnWidth =0, and setting again to Auto, Tried GridView.UpdateLayout().
I solved the above problem my self.
Instead of BindingOperations.ClearAllBindings() i used BindingOperations.ClearBinding(view.GridView, DataGrid.ItemSourceProperty) - which cleared out only ItemSource so that i can regain memory by Items.Clear() for every time i bind the data.
Due to ClearAllBindings, its clears headers panel bindings also, so its looses ParentTemplate.Width property, because of that strange problem happend during horizontal scroll.

Resources