BaiduFMX
513 строк · 14.7 Кб
1// ***************************************************************************
2//
3// FMXComponents: Firemonkey Opensource Components Set
4//
5// CalendarControl is a calendar component like iOS style, it uses
6// listview component internally
7//
8// This component come from xubzhlin's FMX-UI-Controls, collect and
9// arrange by the author's agreement
10// The original project at: https://github.com/xubzhlin/FMX-UI-Controls
11//
12// 该控件来自 xubzhlin的FMX-UI-Controls项目,经作者同意进行收集整理
13// 原项目地址为:https://github.com/xubzhlin/FMX-UI-Controls
14//
15// https://github.com/zhaoyipeng/FMXComponents
16//
17// ***************************************************************************
18// version history
19// 2017-09-11, v0.1.0.0 :
20// first release
21// rename component to TFMXCalendarControl
22// add properties Align, Anchors, ClipChildren, ClipParent, Cursor
23// DragMode, EnableDragHighlight, Enabled, Height, HitTest
24// Locked, Padding, Opacity, Margins, PopupMenu, Position
25// RotationAngle, RotationCenter, Scale, Size, Visible, Width
26// fixed FWeekLayout and FCalenderView stored problem
27// 2017-09-11, v0.2.0.0 :
28// add lunar date option
29// 2017-09-11, v0.3.0.0 :
30// fixed the bug when EndDate's month is December
31// add set month names method
32// 2018-01-10, v0.4.0.0 :
33// add English day support, add Lang property
34
35unit FMX.CalendarControl;
36
37interface
38
39uses
40System.Classes,
41System.SysUtils,
42System.Types,
43System.UITypes,
44System.DateUtils,
45FMX.Platform,
46FMX.Controls,
47FMX.Layouts,
48FMX.Types,
49FMX.Calendar,
50FMX.ListView,
51FMX.ListView.Types,
52FMX.CalendarItemAppearance,
53FMX.ComponentsCommon;
54
55type
56TWeekNames = array [1..7] of string;
57TMonthNames = array [1..12] of string;
58
59const
60TCnWeeks: TWeekNames = ('日', '一', '二', '三', '四', '五', '六');
61TEnWeeks: TWeekNames = ('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT');
62TEsWeeks: TWeekNames = ('dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb');
63TRuWeeks: TWeekNames = ('ВС', 'ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ');
64
65TCnMonths: TMonthNames = ('一月', '二月', '三月', '四月', '五月', '六月',
66'七月', '八月', '九月', '十月', '十一月', '十二月');
67TEnMonths: TMonthNames = (
68'January',
69'February',
70'March',
71'April',
72'May',
73'June',
74'July',
75'August',
76'September',
77'October',
78'November',
79'December'
80);
81TEsMonths: TMonthNames = (
82'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
83'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Ciciembre');
84
85TRuMonths: TMonthNames = ('Январь', 'Февраль', 'Март', 'Апрель', 'Май',
86'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь');
87
88type
89TWeekLayout = class(TControl)
90private
91FWeekNames: TWeekNames;
92procedure DoDrawDay(LocRect:TRectF; AIndex:Integer);
93protected
94procedure Paint; override;
95procedure SetWeekNames(const Names: TWeekNames);
96end;
97
98[ComponentPlatformsAttribute(TFMXPlatforms)]
99TFMXCalendarControl = class(TControl)
100private
101FFirstDayOfWeekNum:Integer;
102FWeekLayout:TWeekLayout;
103FCalenderView:TCalenderView;
104
105FSelectedItem:TClendarDayItem;
106FIsClickDayItem:Boolean;
107FAtPoint:TPointF;
108FOnGetItemIsMark:TOnGetItemIsMark;
109FOnSelectedItem:TNotifyEvent;
110
111FNeedFillDays:Boolean;
112FStartDate: TDate;
113FEndDate: TDate;
114FIsShowLunarDate: Boolean;
115FMonthNames: TMonthNames;
116FLang: string;
117procedure SetSelectedDate(const Value: TDate);
118function DefineItemIndexOfFirstDayInMonth(ADate:TDate):Integer;
119procedure FillDays;
120procedure DoCalenderViewMouseDown(Sender:TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
121procedure DoCalenderViewMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
122procedure DoCalenderViewItemClickEx(const Sender: TObject; ItemIndex: Integer; const LocalClickPos: TPointF; const ItemObject: TListItemDrawable);
123procedure DoGetItemIsMark(ADayItem:TClendarDayItem; var AIsMark:Boolean);
124function GetSelectedDate: TDate;
125procedure SetEndDate(const Value: TDate);
126procedure SetStartDate(const Value: TDate);
127procedure SetIsShowLunarDate(const Value: Boolean);
128procedure SetFirstDayOfWeekNum(const Value: Integer);
129procedure SetLang(const Value: string);
130protected
131procedure Paint; override;
132public
133constructor Create(AOwner: TComponent); override;
134procedure SetMonthNames(const Names: TMonthNames; IsRepaint: Boolean = True);
135procedure SetWeekNames(const Names: TWeekNames; IsRepaint: Boolean = True);
136published
137property Align;
138property Anchors;
139property ClipChildren default False;
140property ClipParent default False;
141property Cursor default crDefault;
142property DragMode default TDragMode.dmManual;
143property EnableDragHighlight default True;
144property Enabled default True;
145property Height;
146property HitTest default True;
147property Locked default False;
148property Padding;
149property Opacity;
150property Margins;
151property PopupMenu;
152property Position;
153property RotationAngle;
154property RotationCenter;
155property Scale;
156property Size;
157property Visible default True;
158property Width;
159property Lang: string read FLang write SetLang;
160property StartDate:TDate read FStartDate write SetStartDate;
161property EndDate:TDate read FEndDate write SetEndDate;
162property FirstDayOfWeek:Integer read FFirstDayOfWeekNum write SetFirstDayOfWeekNum;
163property SelectedDate:TDate read GetSelectedDate write SetSelectedDate;
164property IsShowLunarDate: Boolean read FIsShowLunarDate write SetIsShowLunarDate default true;
165property OnGetItemIsMark:TOnGetItemIsMark read FOnGetItemIsMark write FOnGetItemIsMark;
166property OnSelectedItem:TNotifyEvent read FOnSelectedItem write FOnSelectedItem;
167end;
168
169implementation
170
171{ TWeekLayout }
172
173procedure TWeekLayout.DoDrawDay(LocRect: TRectF; AIndex: Integer);
174begin
175Canvas.Fill.Color:=$FF000000;
176Canvas.FillText(LocRect, FWeekNames[AIndex], False , 1, [], TTextAlign.Center, TTextAlign.Center);
177end;
178
179procedure TWeekLayout.Paint;
180var
181i: Integer;
182ARect:TRectF;
183OffSetX:Single;
184begin
185OffSetX:=ClipRect.Width / 7;
186ARect.Top:=ClipRect.Top;
187ARect.Left:=ClipRect.Left;
188ARect.Height:=ClipRect.Height;
189ARect.Width:= OffSetX;
190for i := 1 to 7 do
191begin
192DoDrawDay(ARect, i);
193ARect.Offset(OffSetX, 0);
194end;
195end;
196
197procedure TWeekLayout.SetWeekNames(const Names: TWeekNames);
198var
199I: Integer;
200begin
201for I := Low(Names) to High(Names) do
202begin
203FWeekNames[I] := Names[I];
204end;
205end;
206
207{ TCalendarControl }
208
209constructor TFMXCalendarControl.Create(AOwner: TComponent);
210var
211LocaleService:IFMXLocaleService;
212begin
213inherited;
214//默认 最近两个月
215FStartDate := Now;
216FEndDate := IncMonth(Now, 1);
217
218FSelectedItem := nil;
219
220if TPlatformServices.Current.SupportsPlatformService(IFMXLocaleService, LocaleService) then
221FFirstDayOfWeekNum := LocaleService.GetFirstWeekday
222else
223FFirstDayOfWeekNum := DayMonday;
224
225FWeekLayout := TWeekLayout.Create(Self);
226FWeekLayout.Height := 28;
227FWeekLayout.Stored := False;
228FWeekLayout.Align := TAlignLayout.Top;
229FWeekLayout.Parent := Self;
230
231FCalenderView := TCalenderView.Create(Self);
232FCalenderView.Stored := False;
233FCalenderView.Align := TAlignLayout.Client;
234FCalenderView.OnMouseDown := DoCalenderViewMouseDown;
235FCalenderView.OnMouseMove := DoCalenderViewMouseMove;
236FCalenderView.OnItemClickEx := DoCalenderViewItemClickEx;
237FCalenderView.OnGetItemIsMark := DoGetItemIsMark;
238FCalenderView.ItemAppearanceName := 'ClendarDayListItem';
239FCalenderView.CanSwipeDelete := False;
240FCalenderView.ShowSelection := False;
241FCalenderView.Parent := Self;
242
243FWeekLayout.Margins.Left := FCalenderView.ItemSpaces.Left;
244FWeekLayout.Margins.Right := FCalenderView.ItemSpaces.Right;
245
246FNeedFillDays:=True;
247FLang := 'zh';
248SetMonthNames(TCnMonths, False);
249SetWeekNames(TCnWeeks, False);
250end;
251
252function TFMXCalendarControl.DefineItemIndexOfFirstDayInMonth(ADate:TDate): Integer;
253var
254Interval: Integer;
255BeginDate: TDateTime;
256begin
257Interval := DaysPerWeek - FFirstDayOfWeekNum;
258BeginDate := RecodeDay(ADate, 1);
259Result := (Interval + DayOfTheWeek(BeginDate)) mod DaysPerWeek;
260end;
261
262procedure TFMXCalendarControl.DoCalenderViewItemClickEx(const Sender: TObject;
263ItemIndex: Integer; const LocalClickPos: TPointF;
264const ItemObject: TListItemDrawable);
265begin
266if FIsClickDayItem and (ItemObject is TClendarDayItem) then
267begin
268if TClendarDayItem(ItemObject).Day = 0 then exit;
269if FSelectedItem <> nil then
270FSelectedItem.IsSelected := False;
271FSelectedItem := TClendarDayItem(ItemObject);
272FSelectedItem.IsSelected := True;
273if Assigned(FOnSelectedItem) then
274FOnSelectedItem(FSelectedItem);
275end;
276end;
277
278procedure TFMXCalendarControl.DoCalenderViewMouseDown(Sender: TObject;
279Button: TMouseButton; Shift: TShiftState; X, Y: Single);
280begin
281FIsClickDayItem := True;
282FAtPoint := TPointF.Create(X, Y);
283end;
284
285procedure TFMXCalendarControl.DoCalenderViewMouseMove(Sender: TObject;
286Shift: TShiftState; X, Y: Single);
287begin
288if FIsClickDayItem then
289begin
290// 偏移了10 认为不是点击
291if (Abs(FAtPoint.X - X) > 10) or (Abs(FAtPoint.Y - Y) > 10) then
292FIsClickDayItem := False;
293end;
294end;
295
296procedure TFMXCalendarControl.DoGetItemIsMark(ADayItem: TClendarDayItem; var AIsMark: Boolean);
297begin
298if Assigned(FOnGetItemIsMark) then
299FOnGetItemIsMark(ADayItem, AIsMark);
300end;
301
302procedure TFMXCalendarControl.FillDays;
303var
304FDate, LastDate: TDate;
305First: Word;
306Year: Word;
307ItemIndex, NewIndex:Integer;
308AItem:TClendarWeekListViewItem;
309procedure FillDaysOfMonth(ADate:TDate);
310var
311DaysInMonthTmp: Word;
312Day:Word;
313I: Word;
314begin
315//FillMonth
316ItemIndex := -1;
317DaysInMonthTmp := DaysInMonth(ADate);
318for I := First to First + DaysInMonthTmp - 1 do
319begin
320NewIndex := i div 7;
321if NewIndex > ItemIndex then
322begin
323AItem := TClendarWeekListViewItem(FCalenderView.Items.Add);
324ItemIndex := NewIndex;
325end;
326Day:= I - First + 1;
327case (i+FFirstDayOfWeekNum) mod 7 of
3280: AItem.SunDayItem.Day := Trunc(RecodeDay(ADate, Day));
3291: AItem.MonDayItem.Day := Trunc(RecodeDay(ADate, Day));
3302: AItem.TurDayItem.Day := Trunc(RecodeDay(ADate, Day));
3313: AItem.WedDayItem.Day := Trunc(RecodeDay(ADate, Day));
3324: AItem.ThuDayItem.Day := Trunc(RecodeDay(ADate, Day));
3335: AItem.RuiDayItem.Day := Trunc(RecodeDay(ADate, Day));
3346: AItem.SatDayItem.Day := Trunc(RecodeDay(ADate, Day));
335end;
336end;
337end;
338procedure FillMonth(ADate:TDate);
339var
340AYear:Word;
341begin
342AYear := YearOf(ADate);
343//FillYear
344if (Year = 0) or (AYear <> Year) then
345begin
346Year:=AYear;
347AItem:=TClendarWeekListViewItem(FCalenderView.Items.Add);
348AItem.Height := 28;
349AItem.YearItem.Year := Year;
350end;
351AItem := TClendarWeekListViewItem(FCalenderView.Items.Add);
352AItem.Text := FMonthNames[MonthOf(ADate)];
353First := DefineItemIndexOfFirstDayInMonth(ADate);
354// case (First + FFirstDayOfWeekNum) mod 7 of
355// 0: AItem.Objects.TextObject.PlaceOffset.X := AItem.SunDayItem.PlaceOffset.X;
356// 1: AItem.Objects.TextObject.PlaceOffset.X := AItem.MonDayItem.PlaceOffset.X;
357// 2: AItem.Objects.TextObject.PlaceOffset.X := AItem.TurDayItem.PlaceOffset.X;
358// 3: AItem.Objects.TextObject.PlaceOffset.X := AItem.WedDayItem.PlaceOffset.X;
359// 4: AItem.Objects.TextObject.PlaceOffset.X := AItem.ThuDayItem.PlaceOffset.X;
360// 5: AItem.Objects.TextObject.PlaceOffset.X := AItem.RuiDayItem.PlaceOffset.X;
361// 6: AItem.Objects.TextObject.PlaceOffset.X := AItem.SatDayItem.PlaceOffset.X;
362// end;
363FillDaysOfMonth(ADate);
364end;
365begin
366FCalenderView.Items.Clear;
367FCalenderView.BeginUpdate;
368try
369Year:=0;
370FDate:=FStartDate;
371
372while (FDate<EndOfTheMonth(FEndDate)) do
373begin
374FillMonth(FDate);
375FDate := IncMonth(FDate);
376end;
377finally
378FCalenderView.EndUpdate;
379end;
380end;
381
382function TFMXCalendarControl.GetSelectedDate: TDate;
383begin
384if FSelectedItem = nil then
385Result :=Trunc(Now)
386else
387Result:=FSelectedItem.Day;
388end;
389
390procedure TFMXCalendarControl.Paint;
391begin
392if FNeedFillDays then
393begin
394FillDays;
395FNeedFillDays := False;
396end;
397inherited;
398end;
399
400procedure TFMXCalendarControl.SetEndDate(const Value: TDate);
401begin
402if (FEndDate <> Value) and (Value > FStartDate) then
403begin
404FEndDate := Value;
405FNeedFillDays := True;
406Repaint;
407end;
408end;
409
410procedure TFMXCalendarControl.SetFirstDayOfWeekNum(const Value: Integer);
411begin
412if FFirstDayOfWeekNum <> Value then
413begin
414FFirstDayOfWeekNum := Value;
415FNeedFillDays := True;
416Repaint;
417end;
418end;
419
420procedure TFMXCalendarControl.SetSelectedDate(const Value: TDate);
421var
422i: Integer;
423AItem: TClendarWeekListViewItem;
424LObject: TClendarDayItem;
425NewDay:Int64;
426begin
427LObject:=nil;
428NewDay:=Trunc(Value);
429if (FSelectedItem = nil) or (FSelectedItem.Day <> NewDay) then
430begin
431if FSelectedItem <> nil then
432FSelectedItem.IsSelected := False;
433for i := 0 to FCalenderView.Items.Count - 1 do
434begin
435AItem := TClendarWeekListViewItem(FCalenderView.Items[i]);
436LObject := AItem.FindDayItem(NewDay);
437if LObject <> nil then
438begin
439FSelectedItem := LObject;
440FSelectedItem.IsSelected := True;
441Break;
442end;
443end;
444end;
445end;
446
447procedure TFMXCalendarControl.SetIsShowLunarDate(const Value: Boolean);
448begin
449if FIsShowLunarDate <> Value then
450begin
451FIsShowLunarDate := Value;
452Repaint;
453end;
454end;
455
456procedure TFMXCalendarControl.SetLang(const Value: string);
457begin
458if FLang <> Value.ToLower then
459begin
460FLang := Value.ToLower;
461if FLang.Equals('en') then
462begin
463SetMonthNames(TEnMonths, False);
464SetWeekNames(TEnWeeks, True);
465end
466else begin
467SetMonthNames(TCnMonths, False);
468SetWeekNames(TCnWeeks, True);
469end;
470Repaint;
471end;
472end;
473
474procedure TFMXCalendarControl.SetMonthNames(const Names: TMonthNames;
475IsRepaint: Boolean);
476var
477I: Integer;
478begin
479for I := Low(Names) to High(Names) do
480begin
481FMonthNames[I] := Names[I];
482end;
483if IsRepaint then
484begin
485FNeedFillDays := True;
486Repaint;
487end;
488end;
489
490procedure TFMXCalendarControl.SetStartDate(const Value: TDate);
491begin
492if (FStartDate <> Value) and (Value < FEndDate) then
493begin
494FStartDate := Value;
495FNeedFillDays := True;
496Repaint;
497end;
498end;
499
500procedure TFMXCalendarControl.SetWeekNames(const Names: TWeekNames;
501IsRepaint: Boolean);
502var
503I: Integer;
504begin
505FWeekLayout.SetWeekNames(Names);
506if IsRepaint then
507begin
508FNeedFillDays := True;
509Repaint;
510end;
511end;
512
513end.
514