Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. issues with MonthGrid

issues with MonthGrid

Scheduled Pinned Locked Moved Solved QML and Qt Quick
8 Posts 3 Posters 785 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • mzimmersM Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by
    #1

    Hi all -

    I'm trying to use MonthGrid as part of a date picker I'm building. I'm having a couple problems. Here's the code:

    MonthGrid {
        id: monthGrid
        delegate: Rectangle {
            id: dayDelegate
            property bool selected: false
    
            Rectangle {
                id: highlightRect
                visible: dayDelegate.selected
            }
            MouseArea {
                onClicked: (mouseEvent) => {
                    dayDelegate.selected = !dayDelegate.selected
               }
            }
        }
        onClicked: (date) => {
            // call a C++ function
            dayDelegate.selected = !dayDelegate.selected // this doesn't work
        }
    }
    

    I realize I can't have two onClicked() slots, but I've tried both and want to show them.

    My first problem is when I select a date (by clicking on it), then change months, the highlighting remains in the grid. Looks like this:
    august.JPG
    Then, when I change months, the highlights remain:
    september.JPG
    It's as though the grid is really just a grid (7 columns and 6 rows) with no real "smarts" behind what it's displaying. Is this expected behavior?

    Second problem is, I can't access the delegate from the latter onClicked() code. I get an error:

    ReferenceError: dayDelegate is not defined
    

    I'm guessing this is a coding error on my part, but I don't know what to do to fix it.

    Any help is appreciated. Thanks...

    JoeCFDJ 1 Reply Last reply
    0
    • mzimmersM mzimmers

      @JoeCFD I see what you mean (I think), but...what's the right way to do this? The MonthGrid seems a little odd in that it can take a delegate, but it's not apparent (to me anyway) what its model is. The docs list a few model properties, but it doesn't seem to behave the way other controls do.

      If it truly is just a grid, then it seems that my highlighting should focus on the grid, not on the dates. So, how do I go about "clearing" the selected property when the month is changed?

      L Offline
      L Offline
      lemons
      wrote on last edited by
      #4

      @mzimmers The documentation shows the properties of the delegate model:
      https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop

      As it is only a data representation of the dates, you have to store the selected date somewhere else and bind the properties:

      property date selectedDate: new Date()
      MonthGrid {
          anchors.fill: parent
          delegate: Item {
              id: delegateItem
      
              // compare dates without time
              property bool isSelectedDay: (
                                  model.year  === selectedDate.getFullYear()
                               && model.month === selectedDate.getMonth()
                               && model.day   === selectedDate.getDate()
                      )
      
              Text {
                  color: delegateItem.isSelectedDay ? "green" : "red"
                  text: model.day
              }
          }
          onPressed: function (date) {
              selectedDate = date
          }
      }
      
      mzimmersM 1 Reply Last reply
      1
      • mzimmersM mzimmers

        Hi all -

        I'm trying to use MonthGrid as part of a date picker I'm building. I'm having a couple problems. Here's the code:

        MonthGrid {
            id: monthGrid
            delegate: Rectangle {
                id: dayDelegate
                property bool selected: false
        
                Rectangle {
                    id: highlightRect
                    visible: dayDelegate.selected
                }
                MouseArea {
                    onClicked: (mouseEvent) => {
                        dayDelegate.selected = !dayDelegate.selected
                   }
                }
            }
            onClicked: (date) => {
                // call a C++ function
                dayDelegate.selected = !dayDelegate.selected // this doesn't work
            }
        }
        

        I realize I can't have two onClicked() slots, but I've tried both and want to show them.

        My first problem is when I select a date (by clicking on it), then change months, the highlighting remains in the grid. Looks like this:
        august.JPG
        Then, when I change months, the highlights remain:
        september.JPG
        It's as though the grid is really just a grid (7 columns and 6 rows) with no real "smarts" behind what it's displaying. Is this expected behavior?

        Second problem is, I can't access the delegate from the latter onClicked() code. I get an error:

        ReferenceError: dayDelegate is not defined
        

        I'm guessing this is a coding error on my part, but I don't know what to do to fix it.

        Any help is appreciated. Thanks...

        JoeCFDJ Offline
        JoeCFDJ Offline
        JoeCFD
        wrote on last edited by
        #2

        @mzimmers said in issues with MonthGrid:

        dayDelegate

        Think about all your delegates have the same id: dayDelegate

        mzimmersM 1 Reply Last reply
        0
        • JoeCFDJ JoeCFD

          @mzimmers said in issues with MonthGrid:

          dayDelegate

          Think about all your delegates have the same id: dayDelegate

          mzimmersM Offline
          mzimmersM Offline
          mzimmers
          wrote on last edited by
          #3

          @JoeCFD I see what you mean (I think), but...what's the right way to do this? The MonthGrid seems a little odd in that it can take a delegate, but it's not apparent (to me anyway) what its model is. The docs list a few model properties, but it doesn't seem to behave the way other controls do.

          If it truly is just a grid, then it seems that my highlighting should focus on the grid, not on the dates. So, how do I go about "clearing" the selected property when the month is changed?

          L 1 Reply Last reply
          0
          • mzimmersM mzimmers

            @JoeCFD I see what you mean (I think), but...what's the right way to do this? The MonthGrid seems a little odd in that it can take a delegate, but it's not apparent (to me anyway) what its model is. The docs list a few model properties, but it doesn't seem to behave the way other controls do.

            If it truly is just a grid, then it seems that my highlighting should focus on the grid, not on the dates. So, how do I go about "clearing" the selected property when the month is changed?

            L Offline
            L Offline
            lemons
            wrote on last edited by
            #4

            @mzimmers The documentation shows the properties of the delegate model:
            https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop

            As it is only a data representation of the dates, you have to store the selected date somewhere else and bind the properties:

            property date selectedDate: new Date()
            MonthGrid {
                anchors.fill: parent
                delegate: Item {
                    id: delegateItem
            
                    // compare dates without time
                    property bool isSelectedDay: (
                                        model.year  === selectedDate.getFullYear()
                                     && model.month === selectedDate.getMonth()
                                     && model.day   === selectedDate.getDate()
                            )
            
                    Text {
                        color: delegateItem.isSelectedDay ? "green" : "red"
                        text: model.day
                    }
                }
                onPressed: function (date) {
                    selectedDate = date
                }
            }
            
            mzimmersM 1 Reply Last reply
            1
            • L lemons

              @mzimmers The documentation shows the properties of the delegate model:
              https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop

              As it is only a data representation of the dates, you have to store the selected date somewhere else and bind the properties:

              property date selectedDate: new Date()
              MonthGrid {
                  anchors.fill: parent
                  delegate: Item {
                      id: delegateItem
              
                      // compare dates without time
                      property bool isSelectedDay: (
                                          model.year  === selectedDate.getFullYear()
                                       && model.month === selectedDate.getMonth()
                                       && model.day   === selectedDate.getDate()
                              )
              
                      Text {
                          color: delegateItem.isSelectedDay ? "green" : "red"
                          text: model.day
                      }
                  }
                  onPressed: function (date) {
                      selectedDate = date
                  }
              }
              
              mzimmersM Offline
              mzimmersM Offline
              mzimmers
              wrote on last edited by mzimmers
              #5

              @lemons thanks for the reply. It took me some experimentation, but I think I finally understand what you're doing.

              Now, I'd like to complicate this slightly -- I'd like to allow the user to select multiple dates. I've added a property to store the selected dates:

              property var selectedDates: []
              

              (I had to use var because I couldn't figure out how to make an array of type date.)

              My onClicked() logic looks like this:

              onClicked: (dateClicked) => {
                             var offset = dateClicked.getTimezoneOffset()
                             var localDate = new Date(dateClicked.getTime() + (offset * 60000))
                             var dateStr = monthGrid.locale.toString(localDate)
                             scheduleModel.enterDate(localDate, true)
                             selectedDate = localDate
                             const index = selectedDates.indexOf(dateStr)
                             //                               console.log("DatePicker.qml: index is " + index)
                             if (index >= 0) {
                                 selectedDates.splice(index, 1) // remove ("deselect")
                             } else {
                                 selectedDates.push(dateStr)
                             }
                             console.log("DatePicker.qml: selectedDate is " + selectedDate + "\n\n")
                         }
              }
              

              (I needed to convert the date to a string because when I used dates, the indexOf() always returned (-1). I think this might be due to the dates being stored as pointers, but this is just a guess.)

              So...if this seems logical so far, the remaining task is to modify the assignment to the property bool isSelectedDay. Any suggestions on this?

              EDIT:

              I've made some progress. I replaced the JS array with a QML list:

                  property list<date> selectedDates: []
              

              so that eliminated the need for using a string representation of the date.

              I also created a function to determine whether a date should be highlighted. It's not particularly efficient, but should be OK:

              function isSelected(date) {
                  var rc = false
                  let len = selectedDates.length
                  for (let i = 0; i < len; i++) {
                      var entry = selectedDates[i]
                      if (
                              entry.getDate() === date.getDate()
                              &&
                              entry.getMonth() === date.getMonth()
                              &&
                              entry.getFullYear() === date.getFullYear()
                              ) {
                          rc = true
                          break
                      }
                  }
                  return rc
              }
              

              My delegate has this property:

              property bool selected: isSelected(model.date)
              

              And it's all working, except for one thing: the highlight is applied to the next date from the one I click on. I'm sure this is because the line above uses model.date instead of the local date. I do create the local date in my onClicked() slot:

              var offset = dateClicked.getTimezoneOffset()
              var localDate = new Date(dateClicked.getTime() + (offset * 60000))
              

              But, I don't know how to expose this JS variable to my QML so I can use it in the above property setting. Can anyone help with this?

              L 1 Reply Last reply
              0
              • mzimmersM mzimmers

                @lemons thanks for the reply. It took me some experimentation, but I think I finally understand what you're doing.

                Now, I'd like to complicate this slightly -- I'd like to allow the user to select multiple dates. I've added a property to store the selected dates:

                property var selectedDates: []
                

                (I had to use var because I couldn't figure out how to make an array of type date.)

                My onClicked() logic looks like this:

                onClicked: (dateClicked) => {
                               var offset = dateClicked.getTimezoneOffset()
                               var localDate = new Date(dateClicked.getTime() + (offset * 60000))
                               var dateStr = monthGrid.locale.toString(localDate)
                               scheduleModel.enterDate(localDate, true)
                               selectedDate = localDate
                               const index = selectedDates.indexOf(dateStr)
                               //                               console.log("DatePicker.qml: index is " + index)
                               if (index >= 0) {
                                   selectedDates.splice(index, 1) // remove ("deselect")
                               } else {
                                   selectedDates.push(dateStr)
                               }
                               console.log("DatePicker.qml: selectedDate is " + selectedDate + "\n\n")
                           }
                }
                

                (I needed to convert the date to a string because when I used dates, the indexOf() always returned (-1). I think this might be due to the dates being stored as pointers, but this is just a guess.)

                So...if this seems logical so far, the remaining task is to modify the assignment to the property bool isSelectedDay. Any suggestions on this?

                EDIT:

                I've made some progress. I replaced the JS array with a QML list:

                    property list<date> selectedDates: []
                

                so that eliminated the need for using a string representation of the date.

                I also created a function to determine whether a date should be highlighted. It's not particularly efficient, but should be OK:

                function isSelected(date) {
                    var rc = false
                    let len = selectedDates.length
                    for (let i = 0; i < len; i++) {
                        var entry = selectedDates[i]
                        if (
                                entry.getDate() === date.getDate()
                                &&
                                entry.getMonth() === date.getMonth()
                                &&
                                entry.getFullYear() === date.getFullYear()
                                ) {
                            rc = true
                            break
                        }
                    }
                    return rc
                }
                

                My delegate has this property:

                property bool selected: isSelected(model.date)
                

                And it's all working, except for one thing: the highlight is applied to the next date from the one I click on. I'm sure this is because the line above uses model.date instead of the local date. I do create the local date in my onClicked() slot:

                var offset = dateClicked.getTimezoneOffset()
                var localDate = new Date(dateClicked.getTime() + (offset * 60000))
                

                But, I don't know how to expose this JS variable to my QML so I can use it in the above property setting. Can anyone help with this?

                L Offline
                L Offline
                lemons
                wrote on last edited by
                #6

                @mzimmers I don't like dates and timezones. My approach would be something like this:

                property list<int> selectedUnixDates
                
                function getLocaleUnix(date) {
                    return (date.getTime() / 1000) + (date.getTimezoneOffset() * 60)
                }
                
                MonthGrid {
                    id: monthGrid
                    anchors.fill: parent
                    delegate: Item {
                        id: delegateItem
                
                        property int localeUnix: getLocaleUnix(model.date)
                        property bool isSelectedDay: selectedUnixDates.includes(localeUnix)
                
                        Text {
                            anchors.centerIn: parent
                            color: delegateItem.isSelectedDay ? "green" : "red"
                            font.bold: delegateItem.isSelectedDay
                            text: model.day
                        }
                    }
                    onClicked: date => {
                                   let localeUnix = getLocaleUnix(date)
                
                                   console.debug("UNIX:", localeUnix)
                                   console.debug("DATE:", new Date(localeUnix * 1000))
                                   /*
                                    clicked on August 16 from client timezone -5
                                    UNIX: 1692162000
                                    DATE: Wed Aug 16 00:00:00 2023 GMT-0500
                                    
                                    clicked on August 16 from client timezone +2
                                    UNIX: 1692136800
                                    DATE: Wed Aug 16 00:00:00 2023 GMT+0200
                                   */
                
                                   let matchIndex = selectedUnixDates.indexOf(localeUnix)
                                   if (matchIndex === -1) {
                                       selectedUnixDates.push(localeUnix)
                                       return
                                   }
                                   selectedUnixDates.splice(matchIndex, 1)
                               }
                }
                
                mzimmersM 1 Reply Last reply
                0
                • L lemons

                  @mzimmers I don't like dates and timezones. My approach would be something like this:

                  property list<int> selectedUnixDates
                  
                  function getLocaleUnix(date) {
                      return (date.getTime() / 1000) + (date.getTimezoneOffset() * 60)
                  }
                  
                  MonthGrid {
                      id: monthGrid
                      anchors.fill: parent
                      delegate: Item {
                          id: delegateItem
                  
                          property int localeUnix: getLocaleUnix(model.date)
                          property bool isSelectedDay: selectedUnixDates.includes(localeUnix)
                  
                          Text {
                              anchors.centerIn: parent
                              color: delegateItem.isSelectedDay ? "green" : "red"
                              font.bold: delegateItem.isSelectedDay
                              text: model.day
                          }
                      }
                      onClicked: date => {
                                     let localeUnix = getLocaleUnix(date)
                  
                                     console.debug("UNIX:", localeUnix)
                                     console.debug("DATE:", new Date(localeUnix * 1000))
                                     /*
                                      clicked on August 16 from client timezone -5
                                      UNIX: 1692162000
                                      DATE: Wed Aug 16 00:00:00 2023 GMT-0500
                                      
                                      clicked on August 16 from client timezone +2
                                      UNIX: 1692136800
                                      DATE: Wed Aug 16 00:00:00 2023 GMT+0200
                                     */
                  
                                     let matchIndex = selectedUnixDates.indexOf(localeUnix)
                                     if (matchIndex === -1) {
                                         selectedUnixDates.push(localeUnix)
                                         return
                                     }
                                     selectedUnixDates.splice(matchIndex, 1)
                                 }
                  }
                  
                  mzimmersM Offline
                  mzimmersM Offline
                  mzimmers
                  wrote on last edited by mzimmers
                  #7

                  @lemons said in issues with MonthGrid:

                  I don't like dates and timezones.

                  Neither do I, but I think I need to find a solution that uses them. This is no longer a MonthGrid issue per se, so I'm going to mark this as solved. Thanks for the help.

                  EDIT:

                  I got it working with the following changes: first I added an offset parameter to my isSelected() function:

                  function isSelected(date, offset) {
                      let rc = false
                      let len = selectedDates.length
                      date.setMinutes(date.getMinutes() + offset)
                      for (let i = 0; i < len; i++) {
                          let entry = selectedDates[i]
                          if (
                                  entry.getDate() === date.getDate()
                                  &&
                                  entry.getMonth() === date.getMonth()
                                  &&
                                  entry.getFullYear() === date.getFullYear()
                                  ) {
                              rc = true
                              break
                          }
                      }
                      return rc
                  }
                  

                  and it's used like so:

                  MonthGrid {
                      id: monthGrid
                      readonly property int offset: new Date().getTimezoneOffset()
                      property date localDate
                  
                      delegate: Rectangle {
                          id: dayDelegate
                  
                          Rectangle {
                              id: highlightRect
                              visible: isSelected(model.date, monthGrid.offset)
                          }
                      }
                  

                  Seems to work fine.

                  Editorial: JS is a maximum PITA. I didn't get to the bottom of this issue until I realized how blithely JS changes variable types at the drop of a hat. In my case, it was cheerfully changing objects to numbers to strings, when I didn't want any of that. I realize that to JS people, "it's not a bug, it's a feature," but to me, it's a nuisance.

                  L 1 Reply Last reply
                  0
                  • mzimmersM mzimmers has marked this topic as solved on
                  • mzimmersM mzimmers

                    @lemons said in issues with MonthGrid:

                    I don't like dates and timezones.

                    Neither do I, but I think I need to find a solution that uses them. This is no longer a MonthGrid issue per se, so I'm going to mark this as solved. Thanks for the help.

                    EDIT:

                    I got it working with the following changes: first I added an offset parameter to my isSelected() function:

                    function isSelected(date, offset) {
                        let rc = false
                        let len = selectedDates.length
                        date.setMinutes(date.getMinutes() + offset)
                        for (let i = 0; i < len; i++) {
                            let entry = selectedDates[i]
                            if (
                                    entry.getDate() === date.getDate()
                                    &&
                                    entry.getMonth() === date.getMonth()
                                    &&
                                    entry.getFullYear() === date.getFullYear()
                                    ) {
                                rc = true
                                break
                            }
                        }
                        return rc
                    }
                    

                    and it's used like so:

                    MonthGrid {
                        id: monthGrid
                        readonly property int offset: new Date().getTimezoneOffset()
                        property date localDate
                    
                        delegate: Rectangle {
                            id: dayDelegate
                    
                            Rectangle {
                                id: highlightRect
                                visible: isSelected(model.date, monthGrid.offset)
                            }
                        }
                    

                    Seems to work fine.

                    Editorial: JS is a maximum PITA. I didn't get to the bottom of this issue until I realized how blithely JS changes variable types at the drop of a hat. In my case, it was cheerfully changing objects to numbers to strings, when I didn't want any of that. I realize that to JS people, "it's not a bug, it's a feature," but to me, it's a nuisance.

                    L Offline
                    L Offline
                    lemons
                    wrote on last edited by lemons
                    #8

                    @mzimmers I think I should have explained my previous example :D

                    To check if the delegate date is in the list of selected dates, you have to do the same formatting in the onClicked as within the delegate, as the delegate date property is the same as the one that gets emitted in the onClicked event.

                    I did the formatting with this function, which gives me the unix timestamp of the start of the date in the client timezone of that day (you might have to read this twice to understand what I want to say):

                    function getLocaleUnix(date) {
                        return (date.getTime() / 1000) + (date.getTimezoneOffset() * 60)
                    }
                    

                    I personally try to avoid date objects in all languages and also store dates as unix timestamps in the databases, as working with integers is way easier and more uniform across different languages.

                    As I converted the dates to an integer, I use a list<int> to store the selected dates, which also offers me to use the JS includes() method, so there is no need of a hard-to-read and maybe more costly method to check if the date is selected.

                    property list<int> selectedUnixDates
                    
                    id: delegateItem
                    property int localeUnix: getLocaleUnix(model.date)
                    property bool isSelectedDay: selectedUnixDates.includes(localeUnix)
                    

                    Also note, your code might not work in all circumstances:

                    MonthGrid {
                        id: monthGrid
                        readonly property int offset: new Date().getTimezoneOffset()
                    

                    e.g. we have winter and summer times, so our timezone offset varies by 1h, depending on the season.
                    → If a user opens the calendar and selects a date in the other season, you might get the date of the day before at 11PM (or the same date but 1AM), as the timezone offset of the monthgrid is related to the current date (date of using the calendar input) and not the selected date / delegate date.

                    EDIT:
                    The console.debug() in my previous example should show how to hook into your other logic by either passing a date or a unix timestamp to C++ or other JS methods.

                    1 Reply Last reply
                    1

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved