Unable to refresh a CalendarView with UpdateLayout() [UWP] - c#

In my UWP app, I'm trying to refresh the density bars on a calendarview if the user clicks a button, the problem is that, although calendarView.UpdateLayout(); should re-run the CalendarViewDayItemChanging event, it runs only when the CalendarView is loaded the first time. Am I doing something wrong?
public MainPage()
{
this.InitializeComponent();
densityColors.Add(Colors.Green);
}
private void CalendarView_CalendarViewDayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs args)
{
item = args.Item;
if (item < DateTimeOffset.Now)
{
item.SetDensityColors(densityColors);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
densityColors[0]=Colors.Blue;
calendarView.UpdateLayout();
}

UpdateLayout won't do anything here since there's no layout update and CalendarViewDayItemChanging is only triggered when you move to another calendar view.
In your case, you just need to manually locate all the CalendarViewDayItems and update their colors on the existing view.
First, create a little Children extension methods to help retrieve all the children of the same type.
public static class Helper
{
public static List<FrameworkElement> Children(this DependencyObject parent)
{
var list = new List<FrameworkElement>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is FrameworkElement)
{
list.Add(child as FrameworkElement);
}
list.AddRange(Children(child));
}
return list;
}
}
Then, simply call it in your button click handler to get all CalendarViewDayItems, loop them through and set each's color accordingly.
var dayItems = calendarView.Children().OfType<CalendarViewDayItem>();
foreach (var dayItem in dayItems)
{
dayItem.SetDensityColors(densityColors);
}
Note you don't need to worry about new CalendarViewDayItems coming into view as they will be handled by your CalendarView_CalendarViewDayItemChanging callback.

Related

WinForms - Disable drag over MenuStrip & other windows

I've made a Windows Forms solution. In the main shell, there is added a MenuStrip, and it's possible to add more Views onto it.
The problem is, that when I add/open a new View, it is opened behind the MenuStrip.
Somehow, I want the MenuStrip to have a border, so it is not possible to drag things behind it, but I have no idea how.
The same case should be with other Views.
You should set the Dock property for the control that you want to add.
OK, I have a solution - I don't totally like it but it works! You will need the usual MDI suspects in terms of flags, etc.
The main form that is the MDI container needs to have something like:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
int BodyCount = 0;
private void fileToolStripMenuItem_Click(object sender, EventArgs e)
{
MDIChildForm child = new MDIChildForm();
child.TitleText = String.Format("Child window {0}", ++BodyCount);
child.MdiParent = this;
child.Show();
}
/*
** This could be fun - shouldn't recurse!
*/
public void ShifTheChild(MDIChildForm spoiltBrat)
{
var m = menuStrip1.Height;
if (spoiltBrat.Location.Y < m)
spoiltBrat.Location = new Point(spoiltBrat.Location.X, 0);
return;
}
}
The child forms need the location changed event hooking:
public partial class MDIChildForm : Form
{
public String TitleText
{
get { return this.Text; }
set { this.Text = value; }
}
MainForm parent = null;
public MDIChildForm()
{
InitializeComponent();
this.ShowIcon = false;
}
private void MDIChildForm_LocationChanged(object sender, EventArgs e)
{
if (parent != null)
parent.ShifTheChild(this);
}
private void MDIChildForm_Load(object sender, EventArgs e)
{
parent = this.MdiParent as MainForm;
}
}
When you move a child into the twilight zone under the menu it will be snapped back out - the method that moves it will cause the event to fire again but the second time nothing should happen (so no recursion).
I don't like this solution simply because I can't get my brain around whether there is a condition that would make it recurse, and I don't like uncertainty.
Good luck.

Add buttons Dynamically at Runtime

I want to add buttons dynamically on c sharp windows form. the no of buttons should be equal the no of records available in data table & i want to display the record whose button is clicked. could anybody help me?
In your case you need to create user control which will represent your item record on UI, create constructor which asspt your item and public event in this user control and add to your container like this.
myPanel.Controls.Add(new ItemRecordUserControl(item));
Probably you will need to use some specific containers instead of regular panel, something like System.Windows.Forms.FlowLayoutPanel.
User control will looks like:
public partial class ItemRecorUserControl : UserControl
{
public event EventHandler<EventArgs> ActionButtonClicked;
public void OnActionButtonClicked(object sender, EventArgs e)
{
if (this.ActionButtonClicked != null)
this.ActionButtonClicked(sender, e);
}
public ItemRecorUserControl()
{
InitializeComponent();
}
public ItemRecorUserControl(ItemRecord item) : this()
{
// fill item data here to controls
}
private void btnAction_Click(object sender, EventArgs e)
{
this.OnActionButtonClicked(sender, e);
}
}
you can add buttons like this :
for (int i = 0; i < YourDataTableItemsCount; i++)
{
Button b = new Button();
b.Left = //Calculate Left
b.Top = //Calculate Top
b.Parent = this;
//Or
this.Controls.Add(b);
}

DeferRefresh Error in C# [duplicate]

In wpf I setup a tab control that binds to a collection of objects each object has a data template with a data grid presenting the data. If I select a particular cell and put it into edit mode, leaving the grid by going to another tab this will cause the exception below to be thrown on returning the datagrid:
'DeferRefresh' is not allowed during an AddNew or EditItem transaction.
It appears that the cell never left edit mode. Is there an easy way to take the cell out of edit mode, or is something else going on here?
Update: It looks like if I do not bind the tab control to the data source, but instead explicitly define each tab and then bind each item in the data source to a content control this problem goes away. This is not really a great solution, so I would still like to know how to bind the collection directly to the tab control.
Update: So what I have actually done for my own solution is to use a ListView and a content control in place of a tab control. I use a style to make the list view look tab like. The view model exposes a set of child view models and allows the user to select one via the list view. The content control then presents the selected view model and each view model has an associated data template which contains the data grid. With this setup switching between view models while in edit mode on the grid will properly end edit mode and save the data.
Here is the xaml for setting this up:
<ListView ItemTemplate="{StaticResource MakeItemsLookLikeTabs}"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding Selected}"
Style="{StaticResource MakeItLookLikeATabControl}"/>
<ContentControl Content="{Binding Selected}">
I'll accept Phil's answer as that should work also, but for me the solution above seems like it will be more portable between projects.
I implemented a behavior for the DataGrid based on code I found in this thread.
Usage:<DataGrid local:DataGridCommitEditBehavior.CommitOnLostFocus="True" />
Code:
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
/// <summary>
/// Provides an ugly hack to prevent a bug in the data grid.
/// https://connect.microsoft.com/VisualStudio/feedback/details/532494/wpf-datagrid-and-tabcontrol-deferrefresh-exception
/// </summary>
public class DataGridCommitEditBehavior
{
public static readonly DependencyProperty CommitOnLostFocusProperty =
DependencyProperty.RegisterAttached(
"CommitOnLostFocus",
typeof(bool),
typeof(DataGridCommitEditBehavior),
new UIPropertyMetadata(false, OnCommitOnLostFocusChanged));
/// <summary>
/// A hack to find the data grid in the event handler of the tab control.
/// </summary>
private static readonly Dictionary<TabPanel, DataGrid> ControlMap = new Dictionary<TabPanel, DataGrid>();
public static bool GetCommitOnLostFocus(DataGrid datagrid)
{
return (bool)datagrid.GetValue(CommitOnLostFocusProperty);
}
public static void SetCommitOnLostFocus(DataGrid datagrid, bool value)
{
datagrid.SetValue(CommitOnLostFocusProperty, value);
}
private static void CommitEdit(DataGrid dataGrid)
{
dataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
private static DataGrid GetParentDatagrid(UIElement element)
{
UIElement childElement; // element from which to start the tree navigation, looking for a Datagrid parent
if (element is ComboBoxItem)
{
// Since ComboBoxItem.Parent is null, we must pass through ItemsPresenter in order to get the parent ComboBox
var parentItemsPresenter = VisualTreeFinder.FindParentControl<ItemsPresenter>(element as ComboBoxItem);
var combobox = parentItemsPresenter.TemplatedParent as ComboBox;
childElement = combobox;
}
else
{
childElement = element;
}
var parentDatagrid = VisualTreeFinder.FindParentControl<DataGrid>(childElement);
return parentDatagrid;
}
private static TabPanel GetTabPanel(TabControl tabControl)
{
return
(TabPanel)
tabControl.GetType().InvokeMember(
"ItemsHost",
BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance,
null,
tabControl,
null);
}
private static void OnCommitOnLostFocusChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var dataGrid = depObj as DataGrid;
if (dataGrid == null)
{
return;
}
if (e.NewValue is bool == false)
{
return;
}
var parentTabControl = VisualTreeFinder.FindParentControl<TabControl>(dataGrid);
var tabPanel = GetTabPanel(parentTabControl);
if (tabPanel != null)
{
ControlMap[tabPanel] = dataGrid;
}
if ((bool)e.NewValue)
{
// Attach event handlers
if (parentTabControl != null)
{
tabPanel.PreviewMouseLeftButtonDown += OnParentTabControlPreviewMouseLeftButtonDown;
}
dataGrid.LostKeyboardFocus += OnDataGridLostFocus;
dataGrid.DataContextChanged += OnDataGridDataContextChanged;
dataGrid.IsVisibleChanged += OnDataGridIsVisibleChanged;
}
else
{
// Detach event handlers
if (parentTabControl != null)
{
tabPanel.PreviewMouseLeftButtonDown -= OnParentTabControlPreviewMouseLeftButtonDown;
}
dataGrid.LostKeyboardFocus -= OnDataGridLostFocus;
dataGrid.DataContextChanged -= OnDataGridDataContextChanged;
dataGrid.IsVisibleChanged -= OnDataGridIsVisibleChanged;
}
}
private static void OnDataGridDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataGrid = (DataGrid)sender;
CommitEdit(dataGrid);
}
private static void OnDataGridIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var senderDatagrid = (DataGrid)sender;
if ((bool)e.NewValue == false)
{
CommitEdit(senderDatagrid);
}
}
private static void OnDataGridLostFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var dataGrid = (DataGrid)sender;
var focusedElement = Keyboard.FocusedElement as UIElement;
if (focusedElement == null)
{
return;
}
var focusedDatagrid = GetParentDatagrid(focusedElement);
// Let's see if the new focused element is inside a datagrid
if (focusedDatagrid == dataGrid)
{
// If the new focused element is inside the same datagrid, then we don't need to do anything;
// this happens, for instance, when we enter in edit-mode: the DataGrid element loses keyboard-focus,
// which passes to the selected DataGridCell child
return;
}
CommitEdit(dataGrid);
}
private static void OnParentTabControlPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var dataGrid = ControlMap[(TabPanel)sender];
CommitEdit(dataGrid);
}
}
public static class VisualTreeFinder
{
/// <summary>
/// Find a specific parent object type in the visual tree
/// </summary>
public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
{
var dObj = VisualTreeHelper.GetParent(outerDepObj);
if (dObj == null)
{
return null;
}
if (dObj is T)
{
return dObj as T;
}
while ((dObj = VisualTreeHelper.GetParent(dObj)) != null)
{
if (dObj is T)
{
return dObj as T;
}
}
return null;
}
}
I have managed to work around this issue by detecting when the user clicks on a TabItem and then committing edits on visible DataGrid in the TabControl. I'm assuming the user will expect their changes to still be there when they click back.
Code snippet:
// PreviewMouseDown event handler on the TabControl
private void TabControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (IsUnderTabHeader(e.OriginalSource as DependencyObject))
CommitTables(yourTabControl);
}
private bool IsUnderTabHeader(DependencyObject control)
{
if (control is TabItem)
return true;
DependencyObject parent = VisualTreeHelper.GetParent(control);
if (parent == null)
return false;
return IsUnderTabHeader(parent);
}
private void CommitTables(DependencyObject control)
{
if (control is DataGrid)
{
DataGrid grid = control as DataGrid;
grid.CommitEdit(DataGridEditingUnit.Row, true);
return;
}
int childrenCount = VisualTreeHelper.GetChildrenCount(control);
for (int childIndex = 0; childIndex < childrenCount; childIndex++)
CommitTables(VisualTreeHelper.GetChild(control, childIndex));
}
This is in the code behind.
This bug is solved in the .NET Framework 4.5. You can download it at this link.
What I think you should do is pretty close to what #myermian said.
There is an event called CellEditEnding end this event would allow you to intercept and make the decision to drop the unwanted row.
private void dataGrid1_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
DataGrid grid = (DataGrid)sender;
TextBox cell = (TextBox)e.EditingElement;
if(String.IsNullOrEmpty(cell.Text) && e.EditAction == DataGridEditAction.Commit)
{
grid.CancelEdit(DataGridEditingUnit.Row);
e.Cancel = true;
}
}

How to find visible DataGrid rows in Silverlight?

How to find visible DataGrid rows in Silverlight?
I'm not sure what you mean by Visible DataGridRows but you could get all DataGridRows that are generated at the moment by finding them in the Visual Tree. This will basically give you all Visible DataGridRows and probably a few more because of the Virtualization used in the DataGrid
Example
private List<DataGridRow> GetDataGridRows(DataGrid dataGrid)
{
return GetVisualChildCollection<DataGridRow>(c_dataGrid);
}
GetVisualChildCollection
public static List<T> GetVisualChildCollection<T>(object parent) where T : FrameworkElement
{
List<T> visualCollection = new List<T>();
GetVisualChildCollection(parent as DependencyObject, visualCollection);
return visualCollection;
}
private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : FrameworkElement
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is T)
{
visualCollection.Add(child as T);
}
else if (child != null)
{
GetVisualChildCollection(child, visualCollection);
}
}
}
The way I've done it is by hooking up to the DataGrid's LoadingRow and UnloadingRow events.
Here's an example
HashSet<DataGridRow> loadedRows
private void HandleUnloadingRow(object sender, DataGridRowEventArgs e)
{
_loadedRows.Remove(e.Row);
}
private void HandleLoadingRow(object sender, DataGridRowEventArgs e)
{
_loadedRows.Add(e.Row);
}

Call Function in Parent Window from Child Window

I'm attempting to implement a custom search dialog in a WPF program. The Parent window is a ListView that is bound to an Observable Collection.
I've made a new window with the search form and it is initialized like so:
searchForm sf = new searchForm(_QPCollection);
sf.Owner = this;
sf.Show();
and I have this function I am trying to call (in the Owner window):
public void selectIndex(int index)
{
ListViewItem toSelect = listView1.Items[index] as ListViewItem;
toSelect.Focus();
}
Then in the Child window (searchForm) attempting to call selectIndex like so:
public void SearchJob_Click(object sender, RoutedEventArgs e)
{
if (sJob.Text == "" || sJob.Text == null) { return; }
for (int i = findCount; i < _QPCollectionSearch.Count; i++)
{
if (i == _QPCollectionSearch.Count - 1) { i = 0; }
if (_QPCollectionSearch[i].jobNumAndFlow.IndexOf(sJob.Text) > -1)
{
findCount = i;
Owner.selectIndex(i);
}
}
}
I get the error: System.Windows.Window does not contain a definition for "selectIndex".
The _QPCollection is the observable collection that the search will loop through. I have the search logic working, but I cannot seem Focus() the index of the ListView in the Parent Window.
My first thought was to have a public function that I could pass the index to and it would do the focus, but I cannot seem to find a way to call function from the Child Window that are in the Parent Window.
Am I approaching this completely wrong? This answer seems to be for WinForms but I am sure there is a way to access the Parent Window and its public functions/properties in WPF.
A cleaner way of handling that scenario would be for your searchForm to raise an event. The parent window could listen for that event and set the focus on its own list view:
public class searchForm
{
public event EventHandler<SearchEventArgs> SearchResultSelected = delegate { };
}
public class SearchEventArgs : EventArgs
{
public int Index { get; set; }
}
searchForm sf = new searchForm(_QPCollection);
sf.SearchResultSelected += (s, e) => MyListView.SelectedIndex = e.Index;
If you set the Owner like you did, you should be able to call public methods inside the dialogue via (Owner as MyWindowDerivative).Method() (if Owner is of type Window), what exactly is stopping you from doing that?
Edit: If you are going to go that route you should make sure that Owner is always of type MyWindowDerivative, e.g. by overwriting the Owner-Property, also prevent parameterless constructors.

Categories