Dear team, this is my problem:
Inside a control I have a generic number (I know it only at runtime) of sub controls.
Each sub-control is a LanguageTextView which contains a rich text editor from Liquid.
A button is used to control how these rich texts have to be shown inside the parent control.
There are 2 options: a single LanguageTextView or all the LanguageTextView items equally spaced inside the parent control.
To solve this problem I used a Grid in the container.
With code behind I populate the grid with rich texts on the "load" event:
foreach (OM_Language lang in LanguageDataManager.INSTANCE.ActiveLanguages) {
LanguageTextView tb = new LanguageTextView();
tb.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
tb.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Stretch;
tb.HorizontalAlignment = HorizontalAlignment.Stretch;
tb.HorizontalContentAlignment = HorizontalAlignment.Stretch;
tb.init(lang);
tb.onTextChangedEvent += new LanguageTextView.onTextChangedEventType(tb_onTextChangedEvent);
if ((_module.Texts != null) && (_module.Texts.ContainsKey(lang.OMID.Value))) {
tb.Text = _module.Texts[lang.OMID.Value];
}
_textBoxList.Add(lang.OMID.Value, tb);
}
_textBoxList is a dictionary which is passed to a grid controller with the grid object to control. Grid controller, called MultiLanguageGridController, handle how LanguageTextView have to be shown inside the grid (a single object or all the objects together).
As first step, when created, grid controller place the LanguageTextView one per column with following code:
int count = 0;
foreach (int key in _uiElements.Keys) {
FrameworkElement fe = _uiElements[key];
ColumnDefinition cd = new ColumnDefinition();
cd.Width = new GridLength(1, GridUnitType.Star);
_cds.Add(key, cd);
_grid.ColumnDefinitions.Add(cd);
_grid.Children.Add(fe);
Grid.SetColumn(fe, count);
Grid.SetRow(fe, 0);
count++;
}
To control layout there are two methods: showAllObjects and showSingleObjet(objectid).
showAllObject is like this:
foreach (int key in _uiElements.Keys) {
_cds[key].Width = new GridLength(1, GridUnitType.Star);
}
showSingleObject is as follow:
foreach(int key in _uiElements.Keys){
if(key == objectId){
_cds[key].Width = new GridLength(1, GridUnitType.Star);
}else{
_cds[key].Width = new GridLength(0);
}
}
When control button is pressed, the button event is handled this way:
ModuleListItemData mld = this.DataContext as ModuleListItemData;
if (mld == null) {
return;
}
mld.IsShowAllLanguages = !mld.IsShowAllLanguages;
IsShowAllLanguages is a property which fires a property changed event.
Property changed event is intercepted and handled this way:
if ("IsShowAllLanguages".Equals(e.PropertyName)) {
if (tmd.IsShowSingleLanguage) {
_gridCtrl.showSingleObject(tmd.SelectedLanguage.OMID.Value);
} else {
_gridCtrl.showAllObjects();
}
return;
}
Where _gridCtrl is a reference to MultiLanguageGridController.
After second round-trip on button event handling, application crashes.
Debugging the application, I setup a break point at the last line of button clich event handler as follows:
private void _btnShowAllLang_Click(object sender, RoutedEventArgs e) {
ModuleListItemData mld = this.DataContext as ModuleListItemData;
if (mld == null) {
return;
}
====> mld.IsShowAllLanguages = !mld.IsShowAllLanguages; <=== Break point here
}
Each time the control button is pressed, break point is reached, but right after exiting from method pressing F10 (visual studio 2010), application stops and after I while I get an exception:
An unhandled exception of type 'System.AccessViolationException' occurred in System.Windows.dll. Trying to read protected memory.
I'm not able to proceed with debugging outside the event handler method!
Any ideas??
Thanks in advance for any support.
Strange but true, the solution was to change XAML inclusion from:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:myConverters="clr-namespace:ArgoWeb.Utility.Data"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignWidth="608" d:DesignHeight="112"
to:
xmlns:my="clr-namespace:ArgoWeb.UI.UserControls.ModuleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:ArgoWeb.Utility.Data"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignWidth="482" d:DesignHeight="144" Height="100"
This solved the problem.
Related
I have a common task. Implement CheckBox checking in DataGrid by one-click. I deside make a DataGridExtended class, derived from DataGrid, and implement something like that:
XAML:
<DataGrid x:Class="DataGrid.DataGridExtended"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
</DataGrid>
CODE:
public partial class DataGridExtended : System.Windows.Controls.DataGrid
{
private int _oldRowIndex;
private int _oldColumnIndex;
public DataGridExtended()
{
MouseLeftButtonUp += DataGridExtendedMouseLeftButtonUp;
MouseLeftButtonDown += DataGridExtendedMouseLeftButtonDown;
}
private void DataGridExtendedMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Если сендер реально DataGridExtended
var dataGridExt = sender as DataGridExtended;
if (dataGridExt == null)
return;
// Получаем текущую ячейку
var currentCell = dataGridExt.CurrentCell;
_oldRowIndex = dataGridExt.SelectedIndex;
_oldColumnIndex = dataGridExt.CurrentColumn.DisplayIndex;
}
private void DataGridExtendedMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Если сендер реально DataGridExtended
var dataGridExt = sender as DataGridExtended;
if (dataGridExt == null)
return;
var rowIndex = dataGridExt.SelectedIndex;
var columnIndex = dataGridExt.CurrentColumn.DisplayIndex;
// Получаем текущую ячейку
var currentCell = dataGridExt.CurrentCell;
//if (_oldRowIndex != rowIndex || _oldColumnIndex != columnIndex)
// return;
// Получаем текущую колонку
var currentColumn = currentCell.Column;
// Получаем контент текущей ячейки
var cellContent = currentColumn.GetCellContent(currentCell.Item);
// Если кликнули по чекбоксу
var checkBox = cellContent as CheckBox;
if (checkBox == null)
return;
// Ставием его в фокус
checkBox.Focus();
// Меняем чек на противоположный
checkBox.IsChecked = !checkBox.IsChecked;
// Получаем выражение привязки для чекбокса
var bindingExpression = checkBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
// Если привязка есть - обновляем ее
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
DataGridExtendedMouseLeftButtonUp handler works fine, but DataGridExtendedMouseLeftButtonDown doesn't firing. And that is the problem.
Without DataGridExtendedMouseLeftButtonDown invoking, checking behaviour is not what I want. Namely, checking is working even I move cursor out from grid :E
Trying to use PreviewMouseLeftButtonDown instead MouseLeftButtonDown give wrong effect :(
So, how can I solve my problem? Don't offer use different approaches to implement one-click checking plz :) Like using XAML-style for example...
In WPF, we often get situations where a particular Click handler appears not to work. The reason for this is usually because a control (or our own code) is handling that event and setting e.Handled = true;, which stops the event from being passed any further. In these situations, it is generally accepted that you should try to access the event before this happens and so we turn to the matching/related Preview event.
In your situation, I would recommend that you use the PreviewMouseLeftButtonDown event. You said that something is not initialized by then, but that doesn't make any sense to me. You said that you need to save the previous value, but you could do that just from your DataGridExtendedMouseLeftButtonUp event handler.
When the user releases the mouse button the first time, then you have their new value. Save this in a variable. When the user releases the mouse button the next and each subsequent time, then save their previous value from the variable as the old value and then read their new value into the variable.
Try MouseDown event and then figure out right or left
I'm trying to use a WPF listbox on a tablet.
In a dummy project I just made a listbox with a lot of items and when i select one this item will be showed in a textblock.
I have a selectionchanged event on the listBox
On my laptop everything works the way it should but when i run it on a tablet the selectionchanged event isn't triggered sporadically. On the screen the old selected item stays selected and the new selected one is highlighted but the item isn't shown in the textblock.
With remote debugging I have seen that the TouchDown, TouchMove and TouchUp event are all triggered, but some times the selectionChanged isn't triggered.
these things I've tried as well:
setting in Xaml inside the listbox:
ScrollViewer.PanningMode="None"
When I do this the selectionchanged event is always triggered but the user can't scroll down anymore with swiping (Which must be possible.
I think here lies the problem somewhere, but I don't have any solution yet.
Help Needed.
After a long time a solution for this problem was found.
first of we need some variables
private TouchPoint _movePoint;
private double _minimum = 0;
private double _maximum;
Me need to catch the TouchMove event of the listBox. This event triggers many times. We need get maximum and minimum Y-values of were the touch has been.
private void myListBox_TouchMove(object sender, TouchEventArgs e)
{
_movePoint := e.GetTouchPoint(myListBox);
if (_minimum.Equals(0))
{
_minimum := _movePoint.Position.Y;
_maximum := _movePoint.Position.Y;
return;
}
if (_movePoint.Position.Y < _minimum)
_minimum := _movePoint.Position.Y;
if (_movePoint.Position.Y > _maximum)
_maximum := _movePoint.Position.Y;
}
Now in the TouchUp event we look at the how far have been slided in the vertical direction. If this is not to big (in this example lower then 20), we gonna look at where the touchup event took place and look for the ListBoxItem that is on that place and set IsSelected=ture on this item.
private void myListBox_TouchUp(object sender, TouchEventArgs e)
{
var difference = _maximum - _minimum;
_maximum = 0;
_minimum=0;
if(difference < 20)
{
var touchPosition = e.GetTouchPoint(myListBox)
UIElement elem = myListBox.InputHitTest(touchPosition.Position) as UIElement;
while (elem != null)
{
if (elem == myListBox)
return;
ListBoxItem item = elem as ListBoxItem;
if (item != null)
{
item.IsSelected = true;
return;
}
elem = VisualTreeHelper.GetParent(elem) as UIElement;
}
}
}
This should work.
I am using WPF datagrid from codeplex. I am using DatagridTemplateColumn and I have written datatemplates to display contents in each column.
Now I have to display some help message to a user when the any control in datagrid is focussed.
For this I thought of using adorner layer. I used ComboBox loaded event and accessed the adrorner layer of it. I then added my own adorner layer with some thing to be displayed there similar to tooltip. Below is the code.
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
if (txtBox == null)
return;
txtBox.ToolTip = comboBox.ToolTip;
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(txtBox);
Binding bind = new Binding("IsKeyboardFocused");
bind.Converter = new KeyToVisibilityConverter();
bind.Source = txtBox;
bind.Mode = BindingMode.OneWay;
PEAdornerControl adorner = new PEAdornerControl(txtBox);
adorner.SetBinding(PEAdornerControl.VisibilityProperty, bind);
PEAdorner layer is this ::
public class PEAdornerControl : Adorner
{
Rect rect;
// base class constructor.
public PEAdornerControl(UIElement adornedElement)
: base(adornedElement)
{ }
protected override void OnRender(DrawingContext drawingContext)
{
.....
}
}
Now the problem is as follows. I am attaching screenshot of how it is looking in datagrid. If the datagrid has more than 4 rows, things are fine.Below is the screenshot
If the datagrid has less number of row, this adorner goes inside datagrid and is not visible to user. The screenshot is below
How do I get this adorner layer above the DataGrid? Please help me !!!
I looked at your question again and i think this is what you would need.
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
if (txtBox == null)
return;
txtBox.ToolTip = comboBox.ToolTip;
//this is locating the DataGrid that contains the textbox
DataGrid parent = FindParent<DataGrid>(this);
//Get the adorner for the parent
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(parent);
Binding bind = new Binding("IsKeyboardFocused");
bind.Converter = new KeyToVisibilityConverter();
bind.Source = txtBox;
bind.Mode = BindingMode.OneWay;
PEAdornerControl adorner = new PEAdornerControl(txtBox);
adorner.SetBinding(PEAdornerControl.VisibilityProperty, bind);
The find parent method is this:
public T FindParent<T>(DependencyObject obj) where T : DepedencyObject
{
if (obj == null)
return null;
DependencyOBject parent = VisualTreeHelper.GetParent(obj);
if (parent is T)
return parent as T;
else
return FindParent<T>(parent);
}
You may need to set the position of your adorner in the OnRender method but this should work. One thing to consider though is that if your DataGrid is within another container (such as a panel, grid, etc) then you may still run into your clipping problem.
The clipping problem is due to the fact that when a container checks the layout of its children it does not normally take into account their adorners. To combat this you would possibly need to create your own control and override the MeasuerOverride(Size constraint) method.
Example:
public class MyPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
Size toReturn = new Size();
foreach (UIElement child in this.InternalChildren)
{
//Do normal Measuring of children
foreach( UIElement achild in AdornerLayer.GetAdorners(child))
//Measure child adorners and add to return size as needed
}
return toReturn;
}
}
That code is really rough for measure but should point you in the right direction. Look at the documentation page http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.measureoverride.aspx for information about measuring child elements in a panel.
Just get the topmost AdornerLayer, instead
static AdornerLayer GetAdornerLayer(FrameworkElement adornedElement)
{
var w = Window.GetWindow(adornedElement);
var vis = w.Content as Visual;
return AdornerLayer.GetAdornerLayer(vis);
}
Also, if you have the name of your DataGrid you can get the nearest layer above it:
AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(myDataGrid);
Using .NET 4 and VS2010 Pro, I've created a very simple WPF Application that contains the following XAML in the MainWindow:
<Grid>
<RichTextBox x:Name="richTextBox"
Margin="2"/>
</Grid>
All I would like to do, and have so far been unsuccessful at doing, is to replace the ContextMenu for the RichTextBox with my own. I've tried the code-behind from MSDN with no luck:
public MainWindow()
{
InitializeComponent();
richTextBox.ContextMenuOpening += new ContextMenuEventHandler(richTextBox_ContextMenuOpening);
}
private void richTextBox_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
RichTextBox rtb = sender as RichTextBox;
if (rtb == null)
{
return;
}
ContextMenu contextMenu = rtb.ContextMenu;
contextMenu.Items.Clear();
MenuItem menuItem = new MenuItem();
menuItem.Header = "Test";
contextMenu.Items.Add(menuItem);
contextMenu.PlacementTarget = rtb;
contextMenu.Placement = PlacementMode.RelativePoint;
TextPointer position = rtb.Selection.End;
if (position == null)
{
return;
}
Rect positionRect = position.GetCharacterRect(LogicalDirection.Forward);
contextMenu.HorizontalOffset = positionRect.X;
contextMenu.VerticalOffset = positionRect.Y;
contextMenu.IsOpen = true;
e.Handled = true;
}
I'm at a loss for what I'm not doing correctly. Does it have to with the MouseDown event being caught by the RTB? Do I have to derive my own version of the RTB and override ContextMenuOpening to get this to work? This seems like something really simple but I'm just not seeing it.
Thanks in advance.
Set the ContextMenu property on your RichTextBox to something other than null:
<RichTextBox x:Name="richTextBox"
Margin="2">
<RichTextBox.ContextMenu>
<ContextMenu/>
</RichTextBox.ContextMenu>
</RichTextBox>
TextBoxBase, the base class of RichTextBox, has logic to automatically supply a context menu with things like Copy and Paste. This logic marks the ContextMenuOpening as handled, so your handler is not invoked. If you assign even an empty ContextMenu to your RichTextBox, it will leave your ContextMenu alone and invoke your handler.
I have a reusable usercontrol that uses a few commands and corresponding keyboard gestures,
(specifically Escape and Ctrl+1...Ctrl+9)
Now as I use this usercontrol in multiple locations I'd like to define the input gestures in the usercontrol, which works fine as long as the focus is within the usercontrol. However, I'd need it to work as long as focus is within the current page/window.
How can I do it, or do I really have to do command/input bindings on every page?
You could handle the Loaded event of the UserControl and walk up the logical tree to find the owning page/window, then you can add the bindings there.
e.g.
public partial class Bogus : UserControl
{
public Bogus()
{
Loaded += (s, e) => { HookIntoWindow(); };
InitializeComponent();
}
private void HookIntoWindow()
{
var current = this.Parent;
while (!(current is Window) && current is FrameworkElement)
{
current = ((FrameworkElement)current).Parent;
}
if (current != null)
{
var window = current as Window;
// Add input bindings
var command = new AlertCommand();
window.InputBindings.Add(new InputBinding(command, new KeyGesture(Key.D1, ModifierKeys.Control)));
}
}
}