Shared Events between multiple instances of WPF Custom Controls - wpf

I have a couple Custom User Controls written in C# using the composite User Control model (i.e. starting with a UserControl) and when used in isolation, I'm not seeing any problems. But when two instances of the same control are used within the same parent, they seem to be stealing each other's events.
Here is an example. I have a control that uses a ListView. On LayoutUpdated event, I am updating the width of the columns to adjust for content. Not that this adds any value to the question, but here's the code of the handler:
var view = transactionListView.View as GridView;
if (view == null) return;
view.Columns[0].Width = view.Columns[0].ActualWidth;
view.Columns[0].Width = double.NaN;
view.Columns[view.Columns.Count - 1].Width = view.Columns[view.Columns.Count - 1].ActualWidth;
view.Columns[view.Columns.Count - 1].Width = double.NaN;
const int dynamicColumnIndex = 2;
try
{
var dynamicColumnWidth = view.Columns.Where((t, index) => dynamicColumnIndex != index)
.Aggregate(transactionListView.ActualWidth, (current, t) => current - t.ActualWidth);
view.Columns[dynamicColumnIndex].Width = dynamicColumnWidth - 25;
}
catch (Exception ex)
{
// log
}
Again, the sample above is just provided to provide some context, but the other controls that I'm referring to contain completely different implementations.
I thought if I provided an x:Name for the ListView within the control, it could help, but it didn't. Then I provided an x:Name for the custom control, when I use it. No difference.
The outcome of when I use this control with the ListView is that the second ListView is the only one that maintains the auto-size on the columns. The first one seems un-impacted.

Related

How to use input bindings for commands when WPF content is hosted in WinForms

I have Windows Forms windows with tab control and one of its tab hosts ElementHost with ContentControl as child element. This control has collection of input bindings which work only when any child element has input focus but if focus is lost they no longer react. Is it possible to define these input bindings on a higher level in such situation?
One solution might be to pass (tunnel) keyboard and/or mouse events from Win Forms to WPF host. I made the following proof of concept. It works but has limitations and requires more work. Firstly you will need a class derived from ElementHost. You should use it in your application instead of the standard ElementHost. This new custom class will have only one additional method ProcessWinFormsKeys which will map WinForms specific keys to those used by WPF. Then it will find a proper input binding and execute it.
public class CustomElementHost : ElementHost
{
public void ProcessWinFormsKeys(Keys keys, Keys modifiers)
{
var key = KeyInterop.KeyFromVirtualKey((int)keys);
var modifier = System.Windows.Input.ModifierKeys.None;
if ((modifiers & Keys.Control) != 0)
modifier |= System.Windows.Input.ModifierKeys.Control;
if ((modifiers & Keys.Shift) != 0)
modifier |= System.Windows.Input.ModifierKeys.Shift;
if ((modifiers & Keys.Alt) != 0)
modifier |= System.Windows.Input.ModifierKeys.Alt;
foreach (InputBinding inputBinding in Child.InputBindings)
{
var keyGesture = inputBinding.Gesture as KeyGesture;
if (keyGesture == null)
continue;
if(keyGesture.Key == key && keyGesture.Modifiers == modifier)
if(inputBinding.Command.CanExecute(inputBinding.CommandParameter))
inputBinding.Command.Execute(inputBinding.CommandParameter);
}
}
}
Then in your Form that contains an instance of CustomElementHost you should subscribe KeyDown event and call ProcessWinFormsKeys method within a handler:
public Form()
{
InitializeComponent();
KeyPreview = true;
KeyDown += Form_KeyDown;
...
}
void Form_KeyDown(object sender, KeyEventArgs e)
{
elementHost.ProcessWinFormsKeys(e.KeyCode, e.Modifiers);
}
Another solution might be to utilize global hot keys. It will require to use RegisterHotKey function from user32.dll. However, I'm not sure if it will work with ElementHost. If you want to try there are a few questions on stackoverflow and dozens of article in Internet.

dynamic tooltips on Winforms controls

I am looking for the cleanest way to bind the same datasource to a control's tooltip that I am binding to the control itself. For example, I have the line
control.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
where dataFeatures is of type BindingSource. I repeat similar lines for many controls on a WinForm Form. Some of these controls can adopt values whose text can span a greater text width than what is visible within the control itself. Instead of redesigning the layout of the form to account for the possibility of partially hidden text in some controls in a few situations, I would like to have the tooltip of each control be bound to the same property of the BindingSource as the controls' EditValue or Text property. Is this possible? I can imagine there is a way to do it by hand by handling the EditValueChanged event like I already do for different reasons, but I was hoping there would be a cleaner solution than having to add new lines of code for each control.
Anybody have a suggestion?
Thanks!
0. For DevExpress controls you can just bind DevExpressControl.ToolTip property to the same value:
devExpressControl.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
devExpressControl.DataBindings.Add(new Binding("ToolTip", dataFeatures, "Key", true, DataSourceUpdateMode.Never));
1. For standard WinForms controls you can use System.Windows.Forms.ToolTip component and its ToolTip.Popup event. For each control set its ToolTip to some value otherwise ToolTip will never appears:
control.DataBindings.Add(new Binding("Text", dataFeatures, "Key", true));
toolTip1.SetToolTip(control, "Some value");
Now you can use ToolTip.Popup event:
private bool _updatingToolTip;
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (_updatingToolTip) return;
//Get binding for Text property.
var binding = e.AssociatedControl.DataBindings["Text"];
if (binding == null) return;
//Get binding value.
var manager = binding.BindingManagerBase;
var itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true);
object value = itemProperty.GetValue(manager.Current);
string toolTipText;
if (value == null || string.IsNullOrEmpty(toolTipText = value.ToString()))
{
e.Cancel = true;
return;
}
//Update ToolTip text.
_updatingToolTip = true;
toolTip1.SetToolTip(e.AssociatedControl, toolTipText);
_updatingToolTip = false;
}
You can easily implement dynamic tooltips with the ToolTipController component. Put this component onto the Form, and assign to each editor via the BaseControl.ToolTipController property.
When it is done, you can handle the ToolTipController.BeforeShow event and change the text according to the control state. The active control is passed through the SelectedControl property of the event parameter.

How to programmatically select a TabItem in WPF TabControl

I would like to know how to select a specific TabItem in a WPF TabControl.
I tried these bellow but nothing work!
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabControl.SelectedValue = MyTabItem
MyTabItem.IsSelected = True
As #Chris says, any of the first three things should work and as #Phyxx says, it doesn't always really work. The problem is some subtle thing about the order of property changes. To work around it you need to let the WPF invoke your tab-selection code in its own time:
Dispatcher.BeginInvoke((Action)(() => MyTabControl.SelectedIndex = x));
This does just what Phyxx' timer does, but in a slightly less extreme way.
All your examples except the third one are correct and will work. The problem must be at another location. Maybe you reset the item after setting or your code never is called?
Valid
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabItem.IsSelected = True
Invalid
MyTabControl.SelectedValue = MyTabItem
Loop through the TabItems and for the tab to be selected, set
tabItem.IsSelected = true
If there are any other place due to binding changing you will see problem. Otherwise, the above code should work.
One thing which hasn't been mentioned above:
The main reason something like this won't work is that the tab items do not have the "Name" property set. Each tab item of the tab control which you want to navigate to programmatically must have its name property set for any of the above code to work.
<tabItem Name="tab1"></tabItem>
I have implemented a small MVVM bindings based solution for selecting tab panels pragmatically.
define a property in your view model - Selected int type
bind the property in your view
<TabControl
x:Name="TabsCandidate"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
SelectedIndex="{Binding Selected}"
private int _selected;
public int Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
Set the value to Select property, simply the binding will activate the tab panel.
if you want to navigate from tab panel inside parent tab panels, this solution will simply works, All you need to do is, access the data context of your control and set it
// set the property value of the view model which points the index of the tab controller.
((CandidateViewModel)((System.Windows.FrameworkElement)candidateTab.Content).DataContext).Selected = CandidateLogTabIndex;
Try to set the MyTabControl.SelectedIndex = x in the event handler of DataContextChanged or Loaded of your UI. Hope this will work.
I tried all the methods that should have worked, but like you nothing actually changed the selected tab. In the end I got it to work by putting the tab selection code in a DispatcherTimer tick.
DispatcherTimer switchTabTimer = new DispatcherTimer();
switchTabTimer.Interval = new TimeSpan(0);
switchTabTimer.Tick += (object timerSender, EventArgs timerE) =>
{
myTabControl.SelectedIndex = 0;
switchTabTimer.Stop();
};
switchTabTimer.Start();
if you don't know the index of the tab (hint its not TabIndex) use:
private async Task ChangeTabTo(TabItem wantedTab) {
int index = 0;
for (var i = 0; i < TabControl.Items.Count; i++) {
var tab = TabControl.Items[i];
var t = tab as TabItem;
if (t == null) continue;
if (t == wantedTab) {
index = i;
break;
}
}
await Dispatcher.BeginInvoke((Action)(() => TabControl.SelectedIndex = index));
}
or modify it to search by name if you don't want to keep a reference to the tab
I'm throwing my 2 cents on the topic, since it might help someone out. I'm using WPF with Prims framework.
I was unable to select a tab by binding to SelectedItem or SelectedIndex - it didn't work. I was also unable to set TabItem.Name value from within TabControl.ItemTemplate or TabControl.ContentTemplate.
Instead I implemented event-based solution:
Add Name value for my TabControl.
Create an event - in Prism that means define a class that derives from PubSubEvent<T> (T is the type of parameter - in my case that was the ViewModel object bound to the TabItem>.
Publish that event whenever I want to a tab to be selected.
Subscribe to the event within my View.cs class and set the TabControl.SelectedItem programmatically using FindName.

Technical code into the ViewModel in wpf?

I have a UserControl with some custom dependency properties bound to a clr property in the ViewModel. The ViewModel has application logic where I deal with the TextPointer/TextRange classes with a FlowDocument.
Should I put that stuff into the code-behind of the UserControl or in the ViewModel?
ranges.Clear();
TextRange range = new TextRange(boundXamlDocument.ContentStart, boundXamlDocument.ContentEnd);
foreach (var block in boundXamlDocument.Blocks)
{
if (block is Paragraph)
{
Paragraph p = block as Paragraph;
//if paragraph has Strikethrough, then do not loop its inlines.
if (p.TextDecorations.Contains(TextDecorations.Strikethrough[0]))
{
TextRange tr = new TextRange(p.ContentStart, p.ContentEnd);
ranges.Add(tr);
}
else
{
foreach (var run in p.Inlines)
{
if (run.TextDecorations.Contains(TextDecorations.Strikethrough[0]))
{
TextRange tr = new TextRange(run.ContentStart, run.ContentEnd);
ranges.Add(tr);
}
}
}
}
}
I only bother to design custom/user controls when I have a concept that won't fit well into any of the usual controls (rare to never), or I have custom control behavior that I want to reuse (more common).
The more abstract your control can be, the more reusable it will be. Although, making it so abstract that no one would get any benefit from it would be doing too much :)
If you have application logic, it is best to define it in the view model (or model) when at all possible. When that logic changes, it won't break other users of your control.
If a feature of the control isn't specific to the exact presentation/user input style, and is specific to that instance of the control, you should probably put it in the view model.
Edit:
From your comments, it seems that the code you are trying to write depends on UI elements (TextBlock text decorators). This means it must and should go in the view.

how to set all row heights of the wpf datagrid when one row height is adjusted

Im using the wpf datagrid and am looking for a way to set the height on all of the rows when the user adjusts one of them. I know the datagrid has a RowHeight property that sets all of the row heights at once, but the how of catching an individual row height changed escapes me
There aren't any events that could be use directly for that. What you could do is use another event that is fired when you resize the rows and other things. The event I'm thinking of right now is PreviewMouseUp, which is release when you release the mouse button anywhere if you datagrid.
What you could do is when the event is fired, you could check the row height of all of your rows and find the one that is different, then update all rows with it.
I arrived on this by trial and error, so long ass you are using an ItemsSource data source it should work fine.
It should work with virtual rows and causes only a brief visual pause and it switches over (this seems mainly down to column autogeneration so can be avoided).
As hacks go it has the advantage of simplicity and the use of mechanics which are not expected to change.
The heuristic on user triggering of the action might be improved but it has not failed on me yet.
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
public static class DataGridExtensions
{
public static void LinkRowHeightsToUserChange(this DataGrid dataGrid)
{
double? heightToApply = null;
bool userTriggered = false;
if (dataGrid.RowHeaderStyle == null)
dataGrid.RowHeaderStyle = new Style(typeof(DataGridRowHeader));
if (dataGrid.RowStyle == null)
dataGrid.RowStyle = new Style(typeof(DataGridRow));
dataGrid.RowStyle.Setters.Add(new EventSetter()
{
Event = DataGridRow.SizeChangedEvent,
Handler = new SizeChangedEventHandler((r, sizeArgs) =>
{
if (userTriggered && sizeArgs.HeightChanged)
heightToApply = sizeArgs.NewSize.Height;
})
});
dataGrid.RowHeaderStyle.Setters.Add(new EventSetter()
{
Event = DataGridRowHeader.PreviewMouseDownEvent,
Handler = new MouseButtonEventHandler(
(rh,e) => userTriggered = true)
});
dataGrid.RowHeaderStyle.Setters.Add(new EventSetter()
{
Event = DataGridRowHeader.MouseLeaveEvent,
Handler = new MouseEventHandler((o, mouseArgs) =>
{
if (heightToApply.HasValue)
{
userTriggered = false;
var itemsSource = dataGrid.ItemsSource;
dataGrid.ItemsSource = null;
dataGrid.RowHeight = heightToApply.Value;
dataGrid.ItemsSource = itemsSource;
heightToApply = null;
}
})
});
}
#Aran
do you remember the rationale behind this?
I can tell you: If you remove both lines to unset and reset the items source (which indeed slows the whole process quite a bit), the row you resize will have its height definitively set.
It seems when you resize a row, you change its Height directly, and this overrides any value you set to the dataGrid's RowHeight property for this row in particular. So basically, here is what you can get :
dataGrid's RowHeight = 20
you change the Height of one Row (say the 5th) to 30 => this row's Height is set to 30 and the dataGrid's RowHeight is set to 30. Everything looking good so far.
now, change another row's Height back to 20 (say the 2nd row). you Set this row's Height to 20 and the DataGrid'RowHeight to 20, which puts all the other rows to 20, EXCEPT the 5th row which stays at 30. (because it had been forced to this value before)
emptying the source and resetting it forces each row to be reloaded and take the dataGrid's RowHeight into account, which eliminates the problem.
As far as I know there is no event that is raised when you resize a row's height.
My first suggestion would be to set the RowStyle in order to create a binding (OneWay) between the the DataGridRow's height property and the datagrid's RowHeight property, but
if you check the Row's height after you resize it, it is unchanged, the ActualHeight is the property that contains the row's "actual" height when you resize it, and ActualHeight cannot be set because "it does not have an accessible set accessor".
After trying this I thought: Where does DataGridRow's ActualHeight gets its value from?
I remembered this post that explains how to detect which cell and row got clicked and also shows the DataGrid's default template visual tree.
By trial and error (using the visual tree image in the link above) I found that it was DataGridCellPresenter that stored the Height that was being used (actually I'm not 100% sure about this, it was the first class that had the height changed up the visual tree since DataGridCell).
Apparently DataGrid doesn't expose the API to get the DataGridCellsPresenter from a DataGridRow (as I found out here)
So my first approach was to get all the DataGridCellPresenter in the DataGrid (through the visual tree) after it has been populated and programatically create a binding between the Height property of the DataGridPresenter and the RowHeight property of the DataGrid.
Here's the code to do that (my DataGrid's instance name is dataGrid1):
Getting all the DataGridCellPresenter:
void GetAllDataGridCellPresenters(DependencyObject parent, List<DataGridCellsPresenter> presenters)
{
int numberOfChildren = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numberOfChildren; i++)
{
if (VisualTreeHelper.GetChild(parent, i) is DataGridCellsPresenter)
{
presenters.Add(VisualTreeHelper.GetChild(parent, i) as DataGridCellsPresenter);
}
else if (VisualTreeHelper.GetChild(parent, i) != null)
{
GetAllDataGridCellPresenters(VisualTreeHelper.GetChild(parent, i), presenters);
}
else
return;
}
}
Setting the bindings programatically on all of them (call this when the Loaded event is raised by the DataGrid):
void SetBindingInDataGridPresenter()
{
List<DataGridCellsPresenter> presenters = new List<DataGridCellsPresenter>();
GetAllDataGridCellPresenters(dataGrid1, presenters);
foreach (DataGridCellsPresenter presenter in presenters)
{
Binding binding = new Binding("RowHeight");
binding.Source = dataGrid1;
binding.Mode = BindingMode.TwoWay;
presenter.SetBinding(DataGridCellsPresenter.HeightProperty, binding);
}
}
(Note: Setting the binding as OneWayToSource didn't work, I really don't know why, I'm probably missing something obvious here...)
This did work... sort of... because I used the Visual Tree to get the DataGridCellsPresenter I only got the visible ones :P, but this shows it can be done this way.
So, finally, the right way to do it would be to supply the DataGrid control template, it can be just as the default one except with the DataGridCellsPresenter's Height property data bound to the RowHeight property of the DataGrid.
I know this does not show exactly how to do it, but you just have to learn (so do I :P)
to redefine a control's template; somehow get the default DataGrid template (or if you're already using another then great, you probably know more than me about it and already know how to do it in order to get the DataGridCellsPresenter Height property automatically bound to the RowHeight DataGrid property) and change it with that bit of magic that gets both height properties bound.

Resources