I have a xaml datagrid containing data, and I am using mvvm.
I would like my cells to change of color after I have edited them. I do not care about saving the color for later use, I just want a visual change right after the content of the cell has been edited.
I was able to achieve the aforementioned behaviour by using a bit of code behind (I wanted to avoid code behind, but since it's purely visual, I guess it's totally fine):
private void MyGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
FrameworkElement element = e.Column.GetCellContent(MyGrid.SelectedItem);
(element.Parent as DataGridCell).Background = (SolidColorBrush)Application.Current.Resources["EditedCellBackground"];
}
This works fine as long as the selected row has the focus. In other words, I can tab back and forth on the same row, and the cell edited has the specified color for background.
Now, the problem is that when I press enter, the row appears to be committed, the cursor moves to the following line, and the background of the edited cell returns back to its original color.
For completeness, here is the datagrid (minus a few columns):
<DataGrid Style="{StaticResource MainContentDataGridTheme}"
ItemsSource="{Binding Source={StaticResource Categories}}"
Grid.Row="1"
x:Name="MyGrid"
CellEditEnding="MyGrid_CellEditEnding">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Name="expander" IsExpanded="True">
<Expander.Header>
<StackPanel>
<TextBlock Text="{Binding Name}" FontWeight="DemiBold" FontSize="13" />
<TextBlock Text="{Binding ItemCount, StringFormat={}Items: {0}}" FontSize="9" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextTrimming="CharacterEllipsis" Text="{Binding}"></TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="25*" Binding="{Binding AppliedPercentage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Header="Applied %">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="UIElement.IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="TextBlock.TextAlignment" Value="Right" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGrid.Columns>
</DataGrid>
Here is the style for the datagrid:
<Style TargetType="DataGrid" x:Key="MainContentDataGridTheme">
<Setter Property="AutoGenerateColumns" Value="False"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="AlternatingRowBackground" Value="{StaticResource DataGridAlternatingRowColor}" />
<Setter Property="Margin" Value="10,10,10,0" />
<Setter Property="CanUserDeleteRows" Value="False" />
<Setter Property="CanUserAddRows" Value="False" />
<Style.Resources>
<Style TargetType="DataGridCell">
<Style.Setters>
<Setter Property="TextBlock.TextAlignment" Value="Right" />
</Style.Setters>
</Style>
</Style.Resources>
</Style>
How can I keep the background of the edited cell while keeping the behaviour of the enter key? I do not mind losing the row commit (the UpdateSourceTrigger takes care of updating my Properties anyway), but I absolutely want to keep the behaviour of the enter key, that is to say: go to the immediate cell down (next row, same column), and be in a position to edit the content right away.
Thanks
After a bit of research, and experimenting with various things, I finally found a workaround that fulfills my requirements.
I added a bit more code behind to do the following:
Disable the row commit to prevent the background of the edited cell to be reset to its original colour.
Catch the KeyUp Event to artificially recreate the behaviour of the enter key.
So, in the xaml, I added the 2 following properties to my datagrid:
RowEditEnding="MyGrid_RowEditEnding"
KeyUp="MyGrid_KeyUp"
And in the code behind, I implemented the corresponding methods:
private void MyGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
// Prevents the row to be committed, but disable the "go to next row" behaviour
e.Cancel = true;
}
private void MyGrid_KeyUp(object sender, KeyEventArgs e)
{
var uiElement = e.OriginalSource as UIElement;
if (e.Key == Key.Enter && uiElement != null)
{
// Handle the key press as normal (-> validate the input)
e.Handled = true;
// Get the next element in the UI
var nextUIElement = uiElement.PredictFocus(FocusNavigationDirection.Down);
// Check if there if the next element is not null. This would occur with the last row of the grid.
if (nextUIElement != null)
{
// Check if the element is a cell, rather than something else like an expander for instance...
if (nextUIElement.GetType().Equals(typeof(DataGridCell)))
{
DataGridCellInfo nextCellInfo = new DataGridCellInfo((DataGridCell)nextUIElement);
// Set the selected row
PrelimsGrid.SelectedItem = nextCellInfo.Item;
// Set the selected cell.
PrelimsGrid.CurrentCell = nextCellInfo;
}
else
{
PrelimsGrid.SelectedItem = uiElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
}
}
}
}
}
Although this works for me, I would humbly admit that I am not a very experienced developer, and that I would be very happy to read about any improvements or alternative solutions.
Related
I understand you can make the whole DataGrid or a whole column readyonly (IsReadOnly = true). However, at cell level this property is ready only. But I do need this level of granularity. There is blog about adding IsReadOnly to a row by changing the source code in old days when DataGrid was public domain, but now I don't have source code for DataGrid. What's workaround?
Making cell disabled (IsEnabled=false) almost meets my need. But the problem is that you can't even click the disabled cell to select the row (I have full row selection mode).
EDIT: Since nobody has responded to this question, so I guess it's not an easy fix. Here is a possible workaround: Make the cell uneditable. The only problem is that clicking the cell doesn't select the row. I just noticed that MouseDown or MouseUp event of the DataGrid is still fired when the disabled cell is clicked. In this event handler, if I could figure out the row it clicked, I could select the row programmatically. However, I couldn't figure out how to find the underlying row from DataGrid.InputHitTest. Can somebody please give me some tip?
After much searching and experimentation using IsTabStop = False and Focusable = False works best for me.
<DataGridTextColumn Header="My Column" Binding="{Binding Path=MyColumnValue}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ReadOnly}" Value="True">
<Setter Property="IsTabStop" Value="False"></Setter>
<Setter Property="Focusable" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
I've encountered the same problem, the cell should be read-only in some rows but not in the others. Here is a workaround solution:
The idea is to dynamically switch the CellEditingTemplate between two templates, one is the same as the one in the CellTemplate, the other is for editing. This makes the edit mode acts exactly the same as the non-editing cell although it is in edit mode.
The following is some sample code for doing this, notice that this approach requires DataGridTemplateColumn:
First, define two templates for read-only and editing cells:
<DataGrid>
<DataGrid.Resources>
<!-- the non-editing cell -->
<DataTemplate x:Key="ReadonlyCellTemplate">
<TextBlock Text="{Binding MyCellValue}" />
</DataTemplate>
<!-- the editing cell -->
<DataTemplate x:Key="EditableCellTemplate">
<TextBox Text="{Binding MyCellValue}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
Then define a data template with additional ContentPresenter layer and use Trigger to switch the ContentTemplate of the ContentPresenter, so the above two templates can be switched dynamically by the IsEditable binding:
<DataGridTemplateColumn CellTemplate="{StaticResource ReadonlyCellTemplate}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!-- the additional layer of content presenter -->
<ContentPresenter x:Name="Presenter" Content="{Binding}" ContentTemplate="{StaticResource ReadonlyCellTemplate}" />
<DataTemplate.Triggers>
<!-- dynamically switch the content template by IsEditable binding -->
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource EditableCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
HTH
There is a property on DataGridCell.IsReadOnly that you might think you can bind to,
e.g. using XAML like this:
<!-- Won't work -->
<DataGrid Name="myDataGrid" ItemsSource="{Binding MyItems}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="IsReadOnly" Value="{Binding MyIsReadOnly}" />
</Style>
</DataGrid.Resources>
<!-- Column definitions... -->
</DataGrid>
Unfortunantly this won't work because this property is not writable.
Next you might attempt to intercept and stop mouse events, but this won't prevent the user from entering edit mode using the F2 key.
The way I sloved this was by listening for the PreviewExecutedEvent on the DataGrid and then conditionally flagging it as handled.
E.g. by adding code similar to this to the constructor of my Window or UserControl (or another more suitable place):
myDataGrid.AddHandler(CommandManager.PreviewExecutedEvent,
(ExecutedRoutedEventHandler)((sender, args) =>
{
if (args.Command == DataGrid.BeginEditCommand)
{
DataGrid dataGrid = (DataGrid) sender;
DependencyObject focusScope = FocusManager.GetFocusScope(dataGrid);
FrameworkElement focusedElement = (FrameworkElement) FocusManager.GetFocusedElement(focusScope);
MyRowItemModel model = (MyRowItemModel) focusedElement.DataContext;
if (model.MyIsReadOnly)
{
args.Handled = true;
}
}
}));
By doing it like this the cells are still focusable and selectable.
But the user will not be able to enter edit mode unless your model items allow it for the given row.
And you will not suffer the performance costs or complexities by using the DataGridTemplateColumn.
I've solved this problem in my application by setting the underlying object in the cell (eg. CheckBox) - IsHitTestVisible = false; Focusable = false;
var cb = this.dataGrid.Columns[1].GetCellContent(row) as CheckBox;
cb.IsHitTestVisible = false;
cb.Focusable = false;
"row" is a DataGridRow. IsHitTestVisible=false means that you can't click/select/manipulate the underlying object via mouse, but you can still select the DataGridCell. Focusable=false means that you can't select/manipulate the underlying object with the keyboard. This gives the illusion of a ReadOnly cell, but you can still select the cell and I'm sure if the DataGrid is set up to SelectionMode=FullRow then clicking the "read only" cell will select the entire row.
My solution is to use binding to the DataGridTemplateColumn with converter.
<UserControl.Resources>
<c:isReadOnlyConverter x:Key="isRead"/>
</UserControl.Resources>
<DataGridTemplateColumn x:Name="exampleTemplate" Header="example:" Width="120" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="exampleCheckBox" VerticalAlignment="Center" IsEnabled="{Binding ElementName=exmpleTemplate, Path=IsReadOnly, Converter={StaticResource isRead}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
and the converter:
class isReadOnlyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
return !(bool)value;
}
catch (Exception)
{
return false;
}
}
This is a bit late but, I was looking into this as well, these solutions work well but I needed something a little different, I did the following and it works exactly like I wanted and what the question is looking for.
I essentially I wanted to be able to enter edit mode for the cell and have all that other templates and command logic the same while not being able to edit the cell.
The solution for all this is to set the TextBox.IsReadOnly property to true in the DataGridCell Style and to handle the initial keydown event
<Style TargetType="DataGridCell">
<Setter Property="TextBox.IsReadOnly" Value="True"/>
<EventSetter Event="PreviewKeyDown" Handler="cell_PreviewKeyDown"/>
</Style>
and the following code behind to stop the initial edit
protected void cell_PreviewKeyDown(object sender, KeyEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell.IsEditing == false &&
((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.Control)) //So that Ctrl+C keeps working
{
cell.IsEditing = true;
e.Handled = true;
}
}
Hopefully this is helpful.
Based on #sohum comment, here you can use simplified version of the response marked as answer.
dataGrid.BeginningEdit += DataGrid_BeginningEdit;
(...)
private static void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
//Actual content of the DataGridCell
FrameworkElement content = e.Column.GetCellContent(e.Row);
MyObject myObject = (MyObject)content.DataContext;
if (!myObject.CanEdit)
{
e.Cancel = true;
}
}
You can use it later as Attached Property Behaviour.
One way of getting selectable, read-only text cells for DataGrid is to use template and style like this:
<DataGrid>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Border Padding="0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBox BorderThickness="0" MouseDoubleClick="DataGrid_TextBox_MouseDoubleClick" IsReadOnly="True" Padding="5" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Text}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
And for CS backend:
private void DataGrid_TextBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
(sender as TextBox).SelectAll();
}
You can do this with a simpler data template.
<DataGrid.Resources>
<DataTemplate x:Key="MyTemplate" DataType="MyRowDataType">
<TextBox Text="{Binding Value}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</DataGrid.Resources>
...
<DataGridTemplateColumn CellTemplate="{StaticResource MyTemplate}" />
For me, the most simple solution was to style the TextBox inside the EditingElementStyle.
<DataGridTextColumn Binding="{Binding MyValueProperty}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding MyReadonlyProperty}" Value="True">
<Setter Property="IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
In my case I was using DataGridTextColumn. I set the IsEnabled property on ContentPresenter in Style as follows and it works fine
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}" >
<ContentPresenter IsEnabled="{Binding Path=IsEditable}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGridTextColumn Header="A"
Binding="{Binding Path=A}"/>
</DataGrid>
I know it is possible to do this by working with ICommand, but since this is the only place in my whole project where this is needed, I am asking myself, if there is maybe a better solution than implementing RelayCommand and ICommand and an additional method in my otherwise property-only class?
Maybe my scenario may help here:
I have a ListView which is bound to a list of properties (this is a custom-class I made to display those).
I have a button in eacht row of entries, that I want to set the "IsToDelete"-Property to true.
If "IsToDelete" is true, all the controls I show in my ListView will collapse.
The whole logic is up to this point in DataTemplates, the one for my button looks like this:
<DataTemplate x:Key="DeleteButtonTemplate">
<Button Name="bt_Delete"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListView}, Path=ItemSource.DeleteClicked}">
<Image Height="16" Width="16" Source="Images\RecycleBin\VSO_RecycleBin_16x.png"/>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsToDelete}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
Note the command in this DataTemplate is currently not working as intended, that is why I even got to the question ;)
In the optimal case I'd just have to do something like:
<Setter Property="{Binding IsToDelete}" Value="True"/>
So, is there a way to solve it like this or at least in a less complicated way than ICommand with RelayCommand?
why not use ToggleButton and bind IsChecked property to IsToDelete property of a viewModel?
simplified example with ItemsControl
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ToggleButton Content="X" IsChecked="{Binding Path=IsToDelete}"/>
<Border Width="100"
Background="MediumPurple"
Margin="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsToDelete}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You could use the Click eventhandler of the Button. Then you don't need any Commands.
private void bt_Delete_Click(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
YourClass dc = btn.DataContext as YourClass;
dc.IsToDelete = true;
}
I don't really like this solution, but I think, it would work.
I have following listview with a gridview column and cell template for that gridview column. But when I click on the "hyperlink", the corresponding gridview row is not getting selected.
Could anyone give me a solution please
DATA TEMPLATE
<DataTemplate x:Key="smTemplate">
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="{Binding SM}" />
<TextBlock>
<Hyperlink x:Name="tHLink" Click="thL_Click" KeyboardNavigation.IsTabStop="True">
<TextBlock Text="TH" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
LIST VIEW
<ListView Focusable="True">
<ListView.View>
<GridView>
<GridViewColumn Header="DM" CellTemplate="{StaticResource smTemplate}" />
</GridView>
</ListView.View>
</ListView>
try changing ListViewItem style so when it gains keyboard focus it will be automatically selected:
<ListView>
<!-- ..... -->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<EventTrigger RoutedEvent="PreviewGotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Hyperlink overrides OnMouseLeftButtonDown and OnMouseLeftButtonUp. In those methods, e.Handled = true is set after processing your event handlers, so you can't override it by attaching events to the control in XAML. In order to fix the behavior to suit your needs, you'll need to create a class derived from Hyperlink and override mouse handling events.
However, all this is unnecessary. Judging by you code, Hyperlink causes more problems than it solves, so I suggest replacing it with a single TextBlock control with a custom style.
<TextBlock Text="TH" TextDecorations="Underline" Cursor="Hand"
Foreground="{x:Static SystemColors.HotTrackBrush}"/>
[Not a good answer]
I find a good solution here. and I just copied and pasted following code and I used it. its worked. even if you click on white blank , the corresponding row will be selected.
<ControlTemplate TargetType="ListViewItem" x:Key="rowStyle1">
<Grid x:Name="backGroundPanel">
<GridViewRowPresenter Content="{TemplateBinding Content}" />
<Rectangle Fill="White" Opacity="0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backGroundPanel" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="columnHeaderContianerStyle" TargetType="ListViewItem">
<Setter Property="Template" Value="{StaticResource ResourceKey=rowStyle1}"/>
</Style>
and I set property "ItemContainerStyle" of listview like code below:
<ListView ItemContainerStyle="{DynamicResource columnHeaderContianerStyle}" ... >
[a revised answer]
The above solution is not working if we are interested in capturing mouse in column control. because the rectangle in backGroundPanel capture mouse events ,then it prevents mouse events being captured by GridViewRowPresenter. so we must use a strategy that have 2 features:
Mouse events should be captured by inner column controls.
When user clicked every Where in the row, The row must be selected.
so, the solution get simpler this time. I used below code for my ListView's ItemContainerStyle.
<Style x:Key="columnHeaderContianerStyle" TargetType="ListViewItem">
<EventSetter Event="PreviewMouseDown" Handler="ListViewItem_PreviewMouseDown"></EventSetter>
</Style>
and event Handler is :
private void ListViewItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListViewItem)
{
ListViewItem s = (ListViewItem)sender;
s.IsSelected = true;
}
}
It works fine with 2 mentioned features.
I'm working on dragging objects around a Canvas, which are encapsulated in ListBoxItems -- the effect being to create a simple pseudo desktop.
I have a ListBox with a Canvas as the ItemsPanelTempalte, so that the ListBoxItems can appear anywhere on screen:
<ListBox ItemsSource="{Binding Windows}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I have a Style to define how the ListBoxItems should appear:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<local:PseudoWindowContainer Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The "PseudoWindowContainer" extends from the ContentControl and has its own Style applied to make it look like a dialog box (title bar, close button, etc...). Here is a chunk of it:
<Style TargetType="{x:Type local:PseudoWindowContainer}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Width" Value="{Binding Width, Mode=TwoWay}" />
<Setter Property="Height" Value="{Binding Height, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PseudoWindowContainer}">
<Grid Name="LayoutRoot" Background="White">
<!-- ... snip ... -->
<Border Name="PART_TitleBar" Grid.Row="0" Background="LightGray" CornerRadius="2,2,0,0" VerticalAlignment="Stretch" Cursor="Hand" />
<TextBlock Name="TitleBar_Caption" Text="{Binding DisplayName}" Grid.Row="0" Background="Transparent" Padding="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<Button Name="TitleBar_CloseButton" Command="{Binding CloseCommand}" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,5,5,0" Width="20" Height="20" Cursor="Hand" Background="#FFFF0000" Foreground="#FF212121" />
<!-- ContentPresenter -->
<ContentPresenter Grid.Row="1" />
<!-- ... snip ... -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="WindowBorder" Property="Background" Value="Blue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="WindowBorder" Property="Background" Value="#22000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Inside the PseudoWindowContainer.cs class I create some event handlers to listen for MouseDown/MouseUp/MoveMove events:
public override void OnApplyTemplate()
{
_titleBar = (Border)Template.FindName("PART_TitleBar", this);
if (_titleBar != null)
{
_titleBar.MouseDown += TitleBar_MouseDown;
_titleBar.MouseUp += TitleBar_MouseUp;
}
_grip = (ResizeGrip)Template.FindName("PART_ResizeGrip", this);
if (_grip != null)
{
_grip.MouseLeftButtonDown += ResizeGrip_MouseLeftButtonDown;
_grip.MouseLeftButtonUp += ResizeGrip_MouseLeftButtonUp;
}
base.OnApplyTemplate();
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove += TitleBar_MouseMove;
((Border)sender).CaptureMouse();
_windowLocation.X = Left;
_windowLocation.Y = Top;
_clickLocation = this.PointToScreen(Mouse.GetPosition(this));
}
private void TitleBar_MouseUp(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove -= TitleBar_MouseMove;
((Border)sender).ReleaseMouseCapture();
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
Point currentLocation = this.PointToScreen(Mouse.GetPosition(this));
Left = _windowLocation.X + currentLocation.X - _clickLocation.X;
Top = _windowLocation.Y + currentLocation.Y - _clickLocation.Y;
}
The trouble I run into is the "Left" and "Top" are not defined properties, and updating them to Canvas.SetLeft/SetTop (or GetLeft/GetTop, accordingly) does not update the position on the Canvas.
I have "Left" and "Top" defined in the ViewModel of the controls I place into the ListBoxItems, and are thus subsequently wrapped with a PseudoWindowContainer because of the Template. These values are being honored and the objects do appear in the correct location when the application comes originally.
I believe I need to somehow define "Left" and "Top" in my PseudoWindowContainer (aka: ContentControl) and have them propagate back up to my ViewModel. Is this possible?
Thanks again for any help!
I've found a solution to the problem. Instead of typing it all out again, I will point to the MSDN Forum post that describes what I did:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/d9036b30-bc6e-490e-8f1e-763028a50153
Did you read article by Bea Stollnitz: The power of Styles and Templates in WPF?
That is an example of Listbox where items are drawn at specific coordinates calculated in Converter.
I want to use LoadingRowGroup event in SilverLight DataGrid to display a group summary.
I have an event:
void dataGrid1_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
// e.RowGroupHeader
}
but I don't know how to use e.RowGroupHeader to set group header value. Maybe I should use e.RowGroupHeader.Template, but I don't know how to set a template by code.
Since nobody has helped me, I found a solution by myself :)
In fact there are two ways:
1) by using LoadingRowGroup event in DataGrid:
void dataGrid1_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
e.RowGroupHeader.Template = (ControlTemplate)System.Windows.Markup.XamlReader.Load(
#"<ControlTemplate xmlns=""http://schemas.microsoft.com/client/2007"">
<StackPanel Orientation=""Horizontal"" Background=""LightGray"">
<TextBlock Text=""Name of group: "" HorizontalAlignment=""Left""/>
<TextBlock Text=""{Binding Name}"" HorizontalAlignment=""Left""/>
</StackPanel>
</ControlTemplate>");
}
2) By setting a Style of DataGridRowGroupHeader:
<data:DataGrid.RowGroupHeaderStyles>
<Style TargetType="data:DataGridRowGroupHeader">
<Setter Property="SublevelIndent" Value="0" />
<Setter Property="Height" Value="30" />
<Setter Property="IsEnabled" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal" Background="LightGray">
<TextBlock Text="Name of group: " HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Left"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</data:DataGrid.RowGroupHeaderStyles>
The (2) way is better for static elements. But the first one can be used when you want to generate headers in a more dynamic way.