Update UI when adding to observable collection - wpf

iv'e got 2 itemscontrols bound to 2 observable collections
my ItemsControls :
<ItemsControl Grid.Column="4" Name="Pipe19" ItemsSource="{Binding Path=Pipes[19].Checkers}" Style="{StaticResource ItemsControlStyle}" ItemsPanel="{StaticResource TopPipePanelTemplate}" />
<ItemsControl Grid.Column="5" Name="Pipe18" ItemsSource="{Binding Path=Pipes[18].Checkers}" Style="{StaticResource ItemsControlStyle}" ItemsPanel="{StaticResource TopPipePanelTemplate}" />
their definitions through style :
<Style TargetType="{x:Type ItemsControl}" x:Key="ItemsControlStyle">
<Setter Property="ItemTemplate" Value="{StaticResource PipeDataItem}"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ItemsControl_MouseLeftButtonDown"></EventSetter>
<EventSetter Event="Drop" Handler="ItemsControl_Drop"></EventSetter>
</Style>
both are considered drop targets , they contain ellipse items which i need to drag from one to the other . the problem is the Add part of the operation doesn't get rendered to the UI .
Visual Helper :
when drag drop as ellipse from the right one to the left :
this is the code which grabs the ellipse and saves the source control
private void ItemsControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl control = (ItemsControl)sender;
UIElement element = control.InputHitTest(e.GetPosition(control)) as UIElement;
if (element != null)
{
Ellipse ellipse = element as Ellipse;
DragSource = control;
DragDrop.DoDragDrop(ellipse, ellipse, DragDropEffects.Copy);
}
}
this is the code where the ellipse is dropped onto it's target :
private void ItemsControl_Drop(object sender, DragEventArgs e)
{
ItemsControl target = (ItemsControl)sender;
Ellipse ellipse = (Ellipse)e.Data.GetData(typeof(Ellipse));
((ObservableCollection<Checker>)DragSource.ItemsSource).Remove(ellipse.DataContext as Checker);
((ObservableCollection<Checker>)target.ItemsSource).Add(ellipse.DataContext as Checker);
}
the Checkers collections are described as follows : (Take notice that they are the itemscontrols ItemsSource :
public class Pipe
{
private ObservableCollection<Checker> checkers;
public ObservableCollection<Checker> Checkers
{
get
{
if (checkers == null)
checkers = new ObservableCollection<Checker>();
return checkers;
}
}
}
after the ItemsControl_Drop event the result is that only the remove updated the UI but the add on the target add not (i would expect that a new item would appear on the left one which i called Add on it's itemsource :
Another Visual Aid :
any ideas ?

Maybe the problem is that your adding to the ItemsSource of the ItemsControl instead of directly to the Pipes[#].Checkers.
I notice that the remove method is a remove from DragSource.ItemsSource.
You probably have a DragTargetControl which ItemsSource you should add to, which will update the Binding.
In the way your doing it you might be undoing the Binding if you cast the Binded ItemsSource to an ObservableCollection.

I think I know what is going on. I bet Pipes[] is not an ObservableCollection. Try it with Pipe1 and Pipe2. Remove from Pipe1 and add to Pipe2.

it turns out the ui element some how loses it's reference if you remove it
from one observable collection before adding it to another ,
so i added the ellipse to the target and then removed it from the source .
private void ItemsControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ItemsControl control = (ItemsControl)sender;
UIElement element = control.InputHitTest(e.GetPosition(control)) as UIElement;
if (element != null && (element is Ellipse))
{
Ellipse ellipse = element as Ellipse;
DragSource = control;
DragDrop.DoDragDrop(ellipse, ellipse, DragDropEffects.Move);
}
}
private void ItemsControl_Drop(object sender, DragEventArgs e)
{
ItemsControl target = (ItemsControl)sender;
Ellipse ellipse = (Ellipse)e.Data.GetData(typeof(Ellipse));
ObservableCollection<Checker> collection = target.ItemsSource as ObservableCollection<Checker>;
Checker checker = ellipse.DataContext as Checker;
collection.Add(checker);
((ObservableCollection<Checker>)DragSource.ItemsSource).Remove(ellipse.DataContext as Checker);
}

Related

XAML - How can I use a KeyBinding command to go to a new line in the textbox?

I am simply trying to move to a new line when Return + Shift are pressed.
I got this much from a previous post on here:
<TextBox.InputBindings>
<KeyBinding Key="Return" Modifiers="Shift" Command=" " />
</TextBox.InputBindings>
But I cannot find anywhere that explains how to accomplish the move to a new line within the textbox.
I cannot use the: AcceptsReturn="True" as I want return to trigger a button.
If you don't have an ICommand defined, you can attach a handler for the UIElement.PreviewKeyUp event to the TextBox. Otherwise you will have to define an ICommand implementation and assign it to the KeyBinding.Command so that the KeyBinding can actually execute. Either solution finally executes the same logic to add the linebreak.
You then use the TextBox.AppendText method to append a linebreak and the TextBox.CaretIndex property to move the caret to the end of the new line.
MainWindow.xaml
<Window>
<TextBox PreviewKeyUp="TextBox_PreviewKeyUp" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void TextBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (!e.Key.Equals(Key.Enter)
|| !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Shift))
{
return;
}
var textBox = sender as TextBox;
textBox.AppendText(Environment.NewLine);
textBox.CaretIndex = textBox.Text.Length;
}
}
I found a nice way to do it without using an ICommand.
Simply added this PreviewKeyDown event onto the control in xaml:
PreviewKeyDown="MessageText_PreviewKeyDown"
And this is the C# behind:
private void MessageText_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// Get the textbox
var textbox = sender as TextBox;
// Check if we have pressed enter
if (e.Key == Key.Enter && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
// Add a new line where cursor is
var index = textbox.CaretIndex;
// Insert a new line
textbox.Text = textbox.Text.Insert(index, Environment.NewLine);
// Shift the caret forward to the newline
textbox.CaretIndex = index + Environment.NewLine.Length;
// Mark this key as handled by us
e.Handled = true;
}
}

Programatically Select a combobox item using the SelectedValue WPF

I have a combo box which is loaded through a Datasource(Datatable). On a scenario I want the combo box to load with the desired value which I would be passing as combobox1.SelectedValue = custId (Say it's a customer details). custID is set as SelectedValuePath in XAML. When I set that, I am getting a null exception. Anything I am missing?
My XAML:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="332,42,0,0" Name="cmbCustomerName" VerticalAlignment="Top" Width="240" IsEditable="True" DisplayMemberPath="customername" SelectedValuePath="custid" ItemsPanel="{StaticResource cust}" SelectionChanged="cmbCustomerName_SelectionChanged" AllowDrop="True" FontWeight="Normal" Text="--Select a Customer Name--" IsSynchronizedWithCurrentItem="True" />
UPDATE:
C# code:
public customer(int custid)
{
InitializeComponent();
cmbcustomer.SelectedValue= custid.ToString();
}
private void cmbCustomerName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cmbcustid.SelectedValue= cmbcustomer.SelectedValue;
}
The selectedValue would not work in this scenario,you should use selectedItem instead,for example:
combobox1.SelectedItem = "custId";
or
combobox1.Text = "custId";
or
combobox1.SelectedIndex = combobox1.Items.IndexOf("custId");
or
combobox1.SelectedIndex = combobox1.FindStringExact("custId")
I will suggest you a method
This is hackish. This is bad coding format.
// Remove the handler
cmbcustomer.SelectionChanged -= cmbCustomerName_SelectionChanged;
// Make a selection...
cmbcustomer.SelectedIndex = combobox1.Items.IndexOf(custid.ToString()); //<-- do not want to raise
// SelectionChanged event programmatically here
// Add the handler again.
cmbcustomer.SelectionChanged += cmbCustomerName_SelectionChanged;
As a temporary solution you can try this out
Wait to access any properties of the ComboBoxes until the they have been loaded:
private void cmbCustomerName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbcustid != null && cmbcustid.IsLoaded && cmbcustomer != null && cmbcustomer.IsLoaded)
cmbcustid.SelectedValue = cmbcustomer.SelectedValue;
}
I got the issue resolved by assigning the value in the combox_loaded event, as the combobox was just initialized in the constructor, but it was not loaded from the datasource.

Navigate listboxitems using arrow Keys

My usercontrol has Listbox with Images as ListboxItems, here i'm facing an issue when i navigate listbox items (Images) using "Arrow Keys",i could'n navigate the items that is present in Next row, say for example ,List box contains rows of images*("I have used WrapPanel")*, if i navigate an images using Right arrow key i cant able to move to next row,
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="IsTabStop" Value="True" />
</Style>
</ListBox.ItemContainerStyle>
Based on this answer which almost worked, but not quite.
Put a KeyDown event on your ListBox and use its ItemsCollection to select the next or previous element when you see a Right or Left keypress.
That moves the selection, but not the keyboard focus (dotted line), so you must also call MoveFocus on the element that has Keyboard focus.
private void ListBox_KeyDown( object sender, KeyEventArgs e )
{
var list = sender as ListBox;
switch( e.Key )
{
case Key.Right:
if( !list.Items.MoveCurrentToNext() ) list.Items.MoveCurrentToLast();
break;
case Key.Left:
if( !list.Items.MoveCurrentToPrevious() ) list.Items.MoveCurrentToFirst();
break;
}
e.Handled = true;
if( list.SelectedItem != null )
{
(Keyboard.FocusedElement as UIElement).MoveFocus( new TraversalRequest( FocusNavigationDirection.Next ) );
}
}
Finally, make sure you have IsSynchronizedWithCurrentItem="True" on your ListBox.
This will give you the wrap-around behaviour that you want.

DatagridTemplate column content only visible when row selected and cell clicked

Hi I have a Datagrid that is bound to an ObservableCollection of custom AutoCAD layer objects. 3 of the columns are DataGridTextColumns and work correctly. However I also have a DataGridTemplateColumn that contains a StackPanel containing a label and a Rectangle. I am using the label to display the ACI or RGB value of the layer depending how it is set and displaying the colour in the rectangle. The rectangle has a mouse down event that launches a colour picker dialog so the user can select a new colour for the layer. This functionality works. What doesn't work is that the contents of the cell (the label and rectangle) are only shown in a row that is selected and the cell clicked on whereas they need to be visible at all times.
I have tried using a Grid inside the DataTemplate and using the Grid's FocusManager.Focused element to give the Rectangle Focus but this hasn't changed the behaviour.
<t:DataGrid x:Name="layersGrid" ItemsSource="{Binding Layers}"
SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" SelectionMode="Single">
<t:DataGridTemplateColumn Visibility="Visible">
<t:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=swatch}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Colour.ColourProperty}"/>
<Rectangle Name="swatch" Fill="{Binding Colour, Converter={StaticResource colourConverter}}"
MouseLeftButtonDown="swatch_MouseLeftButtonDown"/>
</StackPanel>
</Grid>
</DataTemplate>
</t:DataGridTemplateColumn.CellEditingTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
Additionally, once you change the colour of the layer in the model view, the rectangle hasn't updated until another row is selected and then the changed one is selected again.
private void swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Colour col = LaunchColourPickerCode();
((LayersModel)this.Resources[MODEL]).SelectedLayer.Colour = col;
}
The problem with them not displaying has been fixed by using CellTemplate instead of a CellEditingTemplate
I adapted surfen's answer on this page to solve the selection problem
How to perform Single click checkbox selection in WPF DataGrid?
Replacing his method with this:
private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
{
if (cell == null || cell.IsEditing || cell.IsReadOnly)
return;
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid == null)
return;
if (!cell.IsFocused)
{
cell.Focus();
}
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
and adding an event on the swatch to obtain the cell it is in
private void swatch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = null;
while (cell == null)
{
cell = sender as DataGridCell;
if (((FrameworkElement)sender).Parent != null)
sender = ((FrameworkElement)sender).Parent;
else
sender = ((FrameworkElement)sender).TemplatedParent;
}
GridColumnFastEdit(cell, e);
}
Also thanks to kmatyaszek

How to add a new line on TAB key in WPF dataGrid

I want to add a new line in my datagrid when I press 'TAB' key on last cell of the datagrid.
I am using MVVM pattern to do this. I have came with a solution, I assinged Tab key to the Input binding of the datagrid:
<DataGrid.InputBindings>
<KeyBinding Command="{Binding Path=InsertNewLineCommand}" Key="Tab"></KeyBinding>
</DataGrid.InputBindings>
And added following code to InsertNewLineCommand:
private void ExecuteInsertNewLineCommand()
{
//Checked is SelectedCell[0] at last cell of the datagrid
{
InsertNewLine();
}
}
But the problem is ON ADDING KEYBINDING='TAB' MY NORMAL TAB FEATURE ON THE GRID DISABLES (MOVING TO NEXT CELL AND SO...)
Just determine if you are on the last column then execute your command.
I am using PreviewKeyDown, so I could test the logic, but you can put that in your executeCommand method. Anyway, this should get you started:
<DataGrid PreviewKeyDown="DataGrid_PreviewKeyDown" SelectionUnit="Cell" ....
private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.Tab)) return;
var dataGrid = (DataGrid) sender;
var current = dataGrid.Columns.IndexOf(dataGrid.CurrentColumn);
var last = dataGrid.Columns.Count - 1;
if (current == last)
ExecuteInsertNewLineCommand();
}

Resources