I want to add a method to the WPF Calendar control to enable it to select many dates at once, and raise the SelectedDatesChangedEvent only once at the end.
The WPF Calendar control allows you to add only one date at a time (ranges are not useful to me). However I might need to add some 1000 dates and I don't want the SelectedDatesChangedEvent handler called 1000 times because in my case it's an expensive operation.
The WPF DataGrid has a very nice feature that allows for this to be done:
public class MyDataGrid : DataGrid
{
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.BeginUpdateSelectedItems();
...
foreach (DateTime date in datesToBeSelected)
this.SelectedDates.Add(date);
...
this.EndUpdateSelectedItems();
}
}
However the Calendar doesn't have anything like DataGrid's BeginUpdateSelectedItems(), so I'm trying to create a workaround by preventing base.OnSelectedDatesChanged() being called until it's all done:
public class MyCalendar : Calendar
{
protected override void OnSelectedDatesChanged(SelectionChangedEventArgs e)
{
if (false == this.temporaryDontReportSelectionChanged)
base.OnSelectedDatesChanged(e); // this is where I get an exception
}
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.temporaryDontReportSelectionChanged = true;
...
foreach (DateTime date in datesToBeSelected)
SelectedDates.Add(date);
...
this.temporaryDontReportSelectionChanged = false;
OnSelectedDatesChanged(
new SelectionChangedEventArgs(
MyCalendar.SelectedDatesChangedEvent,
removedDates.ToList(),
addedDates.ToList()));
}
}
Now my problem is that I'm getting an exception when calling base.OnSelectedDatesChanged():
Unable to cast object of type
'System.EventHandler`1[System.Windows.Controls.SelectionChangedEventArgs]'
to type
'System.Windows.Controls.SelectionChangedEventHandler'.
I suppose I didn't properly create the SelectionChangedEventArgs object near the end, but I have no idea how to do it right. Any help will be appreciated.
Update: Motivated by Jamleck's question, I recreated the problem in a new project, and now have a bit more information to provide. Here's the MyCalendar class:
public class MyCalendar : System.Windows.Controls.Calendar
{
private bool temporaryDontReportSelectionChanged;
public MyCalendar()
{
// removing this line below makes my problem go away and it works ok
this.SelectedDatesChanged += MyCalendar_SelectedDatesChanged;
}
void MyCalendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
}
protected override void OnSelectedDatesChanged(SelectionChangedEventArgs e)
{
if (!temporaryDontReportSelectionChanged)
base.OnSelectedDatesChanged(e);
}
public void ClearAndSelectMany(IEnumerable<DateTime> datesToBeSelected)
{
this.temporaryDontReportSelectionChanged = true;
...
foreach (DateTime date in datesToBeSelected)
SelectedDates.Add(date);
...
this.temporaryDontReportSelectionChanged = false;
OnSelectedDatesChanged(
new SelectionChangedEventArgs(
MyCalendar.SelectedDatesChangedEvent,
removedDates.ToList(),
addedDates.ToList()));
}
}
So if I don't add an event handler to the SelectedDatesChanged event handler, everything works great, but if I do add it, then I get the InvalidCastException described above.
Related
This has baffled me for a while now and I cannot seem to get the grasp of it. I'm using Cell Value Factory to populate a simple one column table and it does not populate in the table.
It does and I click the rows that are populated but I do not see any values in them- in this case String values. [I just edited this to make it clearer]
I have a different project under which it works under the same kind of data model. What am I doing wrong?
Here's the code. The commented code at the end seems to work though. I've checked to see if the usual mistakes- creating a new column instance or a new tableview instance, are there. Nothing. Please help!
//Simple Data Model
Stock.java
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getstockTicker() {
return stockTicker.get();
}
public void setstockTicker(String stockticker) {
stockTicker.set(stockticker);
}
}
//Controller class
MainGuiController.java
private ObservableList<Stock> data;
#FXML
private TableView<Stock> stockTableView;// = new TableView<>(data);
#FXML
private TableColumn<Stock, String> tickerCol;
private void setTickersToCol() {
try {
Statement stmt = conn.createStatement();//conn is defined and works
ResultSet rsltset = stmt.executeQuery("SELECT ticker FROM tickerlist order by ticker");
data = FXCollections.observableArrayList();
Stock stockInstance;
while (rsltset.next()) {
stockInstance = new Stock(rsltset.getString(1).toUpperCase());
data.add(stockInstance);
}
} catch (SQLException ex) {
Logger.getLogger(WriteToFile.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Connection Failed! Check output console");
}
tickerCol.setCellValueFactory(new PropertyValueFactory<Stock,String>("stockTicker"));
stockTableView.setItems(data);
}
/*THIS, ON THE OTHER HAND, WORKS*/
/*Callback<CellDataFeatures<Stock, String>, ObservableValue<String>> cellDataFeat =
new Callback<CellDataFeatures<Stock, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<Stock, String> p) {
return new SimpleStringProperty(p.getValue().getstockTicker());
}
};*/
Suggested solution (use a Lambda, not a PropertyValueFactory)
Instead of:
aColumn.setCellValueFactory(new PropertyValueFactory<Appointment,LocalDate>("date"));
Write:
aColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
For more information, see this answer:
Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
Solution using PropertyValueFactory
The lambda solution outlined above is preferred, but if you wish to use PropertyValueFactory, this alternate solution provides information on that.
How to Fix It
The case of your getter and setter methods are wrong.
getstockTicker should be getStockTicker
setstockTicker should be setStockTicker
Some Background Information
Your PropertyValueFactory remains the same with:
new PropertyValueFactory<Stock,String>("stockTicker")
The naming convention will seem more obvious when you also add a property accessor to your Stock class:
public class Stock {
private SimpleStringProperty stockTicker;
public Stock(String stockTicker) {
this.stockTicker = new SimpleStringProperty(stockTicker);
}
public String getStockTicker() {
return stockTicker.get();
}
public void setStockTicker(String stockticker) {
stockTicker.set(stockticker);
}
public StringProperty stockTickerProperty() {
return stockTicker;
}
}
The PropertyValueFactory uses reflection to find the relevant accessors (these should be public). First, it will try to use the stockTickerProperty accessor and, if that is not present fall back to getters and setters. Providing a property accessor is recommended as then you will automatically enable your table to observe the property in the underlying model, dynamically updating its data as the underlying model changes.
put the Getter and Setter method in you data class for all the elements.
For Winform DataGridView, what I want to do is move the rows up/down by 1 cm in 1 hour. I googled about it but could not find anything which can give me a clue. Just to be more specific, let's say I have a Form open on my machine with DataGridView filled with data. And if I will look at the DataGridView after 1 hour it should be moved by 1cm up/down. The movement should be in such a way that user won't even realize and there won't be any problem while clicking/selecting cells/rows.
Can someone please point me where to start and how can I implement this?
Note: The Grid will stay as it is. Just the rows will go up/down by 1 cm in 1 hour.
Thanks,
MChicago
This code will auto-scroll the DataGridView using a timer: to move 1com in one hour, you will have to find the right value for interval by experimentation..
public partial class Form1 : Form
{
private readonly Timer tmr = new Timer();
private int start;
public Form1()
{
InitializeComponent();
tmr.Interval = 100;
tmr.Tick += scrollGrid;
tmr.Enabled = true;
List<DisplayItem> list = new List<DisplayItem>
{
new DisplayItem("Apple"),
new DisplayItem("Orange"),
new DisplayItem("Banana"),
new DisplayItem("Grape")
};
// Make a long enough list to see the scrolling
dgv.DataSource = list.Concat(list).Concat(list).ToList();
}
private void scrollGrid(object sender, EventArgs e)
{
PropertyInfo verticalOffset = dgv.GetType()
.GetProperty("VerticalOffset", BindingFlags.NonPublic |
BindingFlags.Instance);
start += 1;
verticalOffset.SetValue(this.dgv, start, null);
}
private class DisplayItem
{
public DisplayItem(string s)
{
this.Value = s;
}
public string Value { get; set; }
}
}
I have a XtraGrid with one GridView, with a column with checkbox repository item. Now I am handling the CellValueChanging event because I want to only allow the user to check or uncheck based on calculations on other column values on the same row hence I need the e.RowHandle and e.Column of this event and this cannot be done on the EditValueChanging of the repository control.
Now somewhere my calculations say that user cannot check a particular cell to and I throw a message box and try Me.BandedGridView1.SetRowCellValue(e.RowHandle, e.Column, False) but unfortunately this does not set the value to false of that cell.
I need to do it here and here only because of the huge number of calculations based on other column values and I need to set value of the current cell whose event I'm handling right.
Please help.
I'm using DevExpress 9.2 (no chance of upgrading to higher version)
Try this code it's working perfectly !
private void GridView1_CellValueChanged(object sender, CellValueChangedEventArgs e)
{
if (e.Column.Caption != "yourColumnCaption") return;
GridView1.SetFocusedRowCellValue("yourColumnFieldName", 1);
}
You might want to prevent updates by handling ShowingEditor event.
class TestData
{
public TestData(string caption, bool check)
{
Caption = caption;
Check = check;
}
public string Caption { get; set; }
public bool Check { get; set; }
}
Initialize some test data:
BindingList<TestData> gridDataList = new BindingList<TestData>();
gridDataList.Add(new TestData("First row", true));
gridDataList.Add(new TestData("Second row", true));
gridControl.DataSource = gridDataList;
Handle ShowingEditor. Check if user is allowed to change chechbox. If not, cancel the event.
private void gridView1_ShowingEditor(object sender, CancelEventArgs e)
{
GridView view = sender as GridView;
// Decision to allow edit using view.FocusedRowHandle and view.FocusedColumn
if (view.FocusedColumn.FieldName == "Check")
{
// Allow edit of odd rows only
bool allowEdit = view.FocusedRowHandle % 2 == 1;
e.Cancel = !allowEdit;
}
}
What is the order in which attached properties are applied to an object ? I guess I should ignore this, but here my scenario:
I've got an attached property to stick the VM to the View, and then, another attached property that depend on the first one. I'm trying to see what happen if the second is set up before the first, but I can't manage to get the error! ie the first ( the model ) is always set up before the second, whatever is the order in xaml. Who is driving the order of assigment? Can I change it?
Now I'm dealing with the late assigmement by subscribing the proeprty change event:
DependencyPropertyDescriptor dd = DependencyPropertyDescriptor.FromProperty(FrameworkElement.DataContextProperty,depo.GetType());
dd.AddValueChanged(depo, (s, a) =>
{
ChangeDatacontext(s as DependencyObject);
}
and for simulate the problem I setup manually a new datacontext to the object.
Thanks,
Felix
I can't directly answer this question, because I never rely on which property is set before the other, but you can manage things with a method that both attached properties use.
here is an example from my current code:
public static readonly DependencyProperty RuleVMProperty =
DependencyProperty.RegisterAttached("RuleVM", typeof(DocumentRuleViewModel), typeof(DocumentRuleViewModel), new UIPropertyMetadata(null, RuleVMChanged));
public static void RuleVMChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = GetRefid(sender);
var vm = args.NewValue as DocumentRuleViewModel;
if(vm==null)
return;
vm.SetDocumentFromRefid(sender, el);
}
public static readonly DependencyProperty RefidProperty =
DependencyProperty.RegisterAttached("Refid", typeof(XmlElement), typeof(DocumentRuleViewModel), new UIPropertyMetadata(RefidChanged));
public static void RefidChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var el = args.NewValue as XmlElement;
var vm = GetRuleVM(sender);
if (vm == null)
return;
vm.SetDocumentFromRefid(sender, el);
}
private void SetDocumentFromRefid(DependencyObject sender, XmlElement element)
{
... // this is where the actual logic sits
}
so essentially you have two changed handlers and whichever triggers last executes the logic because it sees if the other property is null.
I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.