μ•„λΉ μ™€λ‚˜ - slog

μ•„λ“€κ³Ό 단 λ‘˜μ΄ λŒ€ν™”λ₯Ό λ‚˜λˆŒ 수 μžˆλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ§Œλ“œλŠ” 것을 λͺ©μ μœΌλ‘œ 슬둜그λ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€.

ν•„μˆ˜ κΈ°λŠ₯

λͺ¨λ°”일 μ•±μ΄λ‚˜ ν™ˆνŽ˜μ΄μ§€, μœˆλ„μš° μ•± ν˜•νƒœ λ“± 자유둜운 ν˜•νƒœλ‘œ λŒ€ν™” μ„œλΉ„μŠ€λ₯Ό μ΄μš©ν•  수 μžˆμ–΄μ•Ό 함

μ „κ°œ

  • Uno ν”Œλž«νΌ μ‚¬μš©
  • DB μ‚¬μš©ν•˜μ§€ μ•ŠμŒ
    • μ•„λ“€κ³Ό λŒ€ν™”ν•  κ²ƒμ΄λ―€λ‘œ λ‘œκ·ΈμΈμ— ν•„μš”ν•œ μ •λ³΄λŠ” 파일 ν˜•νƒœλ‘œ μ €μž₯ 함
    • λŒ€ν™” λ‚΄μš©λ„ 파일둜 μ €μž₯
  • Stl.Fusion을 μ΄μš©ν•΄ μ‹€μ‹œκ°„ κΈ°λŠ₯ κ΅¬ν˜„
  • 졜초 버젼은 일단 λŒ€ν™”λ§Œ μž˜λ˜λ„λ‘ 함.
  • ν™”λ©΄ ꡬ성은 μ‹¬ν”Œν•˜λ˜ κΉ”λ”ν•˜κ²Œ κ΅¬ν˜„. λͺ‡λͺ‡ λ””μžμΈμ€ μ•„λ“€μ—κ²Œ 맑김

ν•„μš” 기술

  • λ©”μ‹œμ§€ μ‹€μ‹œκ°„ μˆ˜μ‹  : Stl.Fusion
  • λ©”μ‹œμ§€ ν‘Έμ‹œ : Uno ?
  • 인증 : ? (λͺ¨λ°”일 및 μ›Ήμ–΄μ…ˆλΈ”λ¦¬, μœˆλ„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜)

μ½”λ“œλͺ…

DnSE (발음 덴슀)

5개의 μ’‹μ•„μš”

ν™”λ©΄ ꡬ성

μŠ€ν”Œλž˜μ‹œ ν™”λ©΄

(μ•„λ“€λ†ˆμ΄ λ°”κΏ€ κ²ƒμ΄λ―€λ‘œ λŒ€μΆ© λ§Œλ“€μ—ˆμŒ)
SplashScreen

인증 ν™”λ©΄

λŒ€ν™” ν™”λ©΄

2개의 μ’‹μ•„μš”

Uno ν”Œλž«νΌ ꡬ성

https://platform.uno/docs/articles/get-started-vs-2022.html

image

κΈ°λ³Έ ν”„λ‘œμ νŠΈ ν…œν”Œλ¦Ώ μ‹€ν–‰ κ²°κ³Ό

μœˆλ„μš°

image

μ•ˆλ“œλ‘œμ΄λ“œ

image

μ›Ήμ–΄μ…ˆλΈ”λ¦¬

image

5개의 μ’‹μ•„μš”

였, μ•„λ“œλ‹˜κ³Ό μž¬λ―Έλ‚œ ν”„λ‘œμ νŠΈμΈκ±° κ°™κ΅°μš” !
μ’‹μ•„ λ³΄μž…λ‹ˆλ‹€ :slight_smile:
μ™„μ„± λ˜μ‹  ν›„ κΈ°νšŒκ°€ λœλ‹€λ©΄ Flutter둜 ν΄λ‘ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

(근데 μ΄λ ‡κ²Œ 개인 slog에 쀑간 λŒ“κΈ€λ‘œ μ΄λ ‡κ²Œ 끼어듀어도 λ˜λŠ”κ±΄κ°€μš”?.)

4개의 μ’‹μ•„μš”

λ„€ κ·ΈλŸΌμš”. 개인 μŠ¬λ‘œκ·Έμ§€λ§Œ λ‹€μ–‘ν•œ μ°Έμ—¬λ₯Ό ν™˜μ˜ν•©λ‹ˆλ‹€ ^^
그런데 10μ‚΄μ§œλ¦¬ μ•„λ“€μ—κ²Œ μ½”λ”© ν₯λ―Έλ₯Ό μœ λ°œν•˜κΈ° μœ„ν•œ ν”„λ‘œμ νŠΈλΌβ€¦ λŒ€λ‹¨ν•œ 것이 μ•„λ‹™λ‹ˆλ‹€. ^^;

2개의 μ’‹μ•„μš”

Uno ν”Œλž«νΌμ„ μ‚¬μš©ν•˜μ‹œλŠ”κ΅°μš”.
Uno ν”Œλž«νΌμ„ μ‚¬μš©ν•΄ λ³΄λŠ” κ²ƒλ§ŒμœΌλ‘œλ„ μ˜λ―Έκ°€ μžˆμ„ κ±° κ°™μŠ΅λ‹ˆλ‹€.

2개의 μ’‹μ•„μš”

μŠ€ν”Œλž˜μ‹œ 슀크린

ν”Œλž«νΌλ§ˆλ‹€ μŠ€ν”Œλž˜μ‹œ μŠ€ν¬λ¦°μ„ λ³΄μ΄λŠ” 방식이 λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— μ™„μ „ μžλ™μ€ μ•„λ‹™λ‹ˆλ‹€.

λ¨Όμ € μŠ€ν”Œλž˜μ‹œ 슀크린으둜 μ‚¬μš©ν•  이미지λ₯Ό Shared ν”„λ‘œμ νŠΈμ˜ Assets 디렉토리에 SplashScreen.png둜 λ°°μΉ˜ν•©λ‹ˆλ‹€.

image

이제 이 μ΄λ―Έμ§€λŠ” ν”Œλž«νΌ 별도 접근이 되게 λ©λ‹ˆλ‹€. μ•ˆλ“œλ‘œμ΄λ“œμ˜ 경우 @drawable/assets_splashscreen둜 접근이 되고 μ›Ήμ–΄μ…ˆλΈ”λ¦¬μ˜ 경우 μŠ€ν”Œλž˜μ‹œ 이미지 κ²½λ‘œκ°€ Assets/SplashScreen.png둜 λ˜μ–΄ 있기 λ•Œλ¬Έμ— λ°”λ‘œ 반영이 λ©λ‹ˆλ‹€.

image

Windows의 κ²½μš°μ—λ„ κΈ°λ³Έ μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. (그런데 WinUI 3μ—μ„œλŠ” μŠ€ν”Œλž˜μ‹œ 화면이 λœ¨μ§€λŠ” μ•ŠλŠ”κ΅°μš”β€¦)

image

μ•ˆλ“œλ‘œμ΄λ“œμ˜ 경우 Styles.xml의 AppTheme μŠ€νƒ€μΌμ— λ‹€μŒμ„ μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.

...
<item name="android:windowSplashScreenAnimatedIcon">@drawable/assets_splashscreen</item>
...

그러면 μ‹œμž‘ν•  λ•Œ μž˜μ€ λ³΄μ΄λŠ”λ° μ‚¬μ΄μ¦ˆκ°€ μ•ˆλ§žκ²Œ 좜λ ₯λ˜λŠ”κ΅°μš”.

μŠ€ν”Œλž˜μ‹œ 화면은 ν”Œλž«νΌ λ§ˆλ‹€ μ‚¬μ΄μ¦ˆκ°€ λ‹€λ₯΄λ―€λ‘œ ν”Œλž«νΌ λ³„λ‘œ μ€€λΉ„ν•˜λŠ”κ²ƒλ„ 방법일 것 κ°™μŠ΅λ‹ˆλ‹€.


https://platform.uno/docs/articles/splash-screen.html?tabs=tabid-vswin

2개의 μ’‹μ•„μš”

ν…Œλ§ˆ

ν…Œλ§ˆ μ‹œμŠ€ν…œμ€ WinUI의 그것과 κ°™μŠ΅λ‹ˆλ‹€. ν…Œλ§ˆ μžμ›μ€ ThemeResource둜 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.
ν˜„μž¬ μ„€μ •λœ ν…Œλ§ˆλŠ” FrameworkElement 별 ActualTheme μ†μ„±μœΌλ‘œ 확인할 수 있고 RequestedTheme μ†μ„±μœΌλ‘œ ν…Œλ§ˆλ₯Ό 지정할 수 μžˆμŠ΅λ‹ˆλ‹€. RequestedTheme둜 μ„€μ •ν•  수 μžˆλŠ” ν…Œλ§ˆλŠ” Default, Light, Dark μž…λ‹ˆλ‹€.

App.Current.RequestedTheme둜 섀정이 되면 μ’‹μ€λ°μš”, 처음 앱이 μ‹œμž‘ν•  λ•Œλ§Œ 이 ν…Œλ§ˆ 섀정이 적용되고 이후 λ³€κ²½ν•˜λŠ”κ²ƒμ€ λ°˜μ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

그리고 μœˆλ„μ˜ 경우 Window.Currentκ°€ nullμž…λ‹ˆλ‹€. μ›Ήμ–΄μ…ˆλΈ”λ¦¬λ‚˜ μ•ˆλ“œλ‘œμ΄λ“œμ—μ„œλŠ” Window.Current둜 μ μš©ν•΄μ•Όλ§Œ ν…Œλ§ˆκ°€ μ μš©λ©λ‹ˆλ‹€. MainPage μΈμŠ€ν„΄μŠ€μ˜ RequestedTheme 속성을 바꿔도 μ μš©λ˜μ§€ μ•ŠλŠ”λ°μš”, κ·Έλ ‡κΈ° 떄문에 λ‹€μŒμ˜ μ½”λ“œλ₯Ό 톡해 ν…Œλ§ˆλ₯Ό 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.

| MainPage.xaml, ν…Œλ§ˆ λ³€κ²½ ν† κΈ€

private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
    var root = (FrameworkElement)Window.Current?.Content ?? this;
    root.RequestedTheme = themeToggleSwitch.IsOn is false ? ElementTheme.Light : ElementTheme.Dark;
}

ν…Œλ§ˆ ν…ŒμŠ€νŠΈ 쀑…

image

image

1개의 μ’‹μ•„μš”

WinUIμ™€μ˜ ν˜Έν™˜μ„±

Uno ν”Œλž«νΌμ€ WinUI의 namespaceλ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€. WinUIλ₯Ό 100% λͺ¨λ‘ μ™„μ „νžˆ μ§€μ›ν•˜μ§€λŠ” μ•Šκ² μ§€λ§Œ 이 말은 XAMLλΆ€ν„° 컨트둀, μ„ΈλΆ€ κ΅¬ν˜„κΉŒμ§€ UWP 및 WinUI 3의 κΈ°μ‘΄ κ΅¬ν˜„λœ μ½”λ“œλ₯Ό 거의 κ·ΈλŒ€λ‘œ κ°€μ Έλ‹€κ°€ μ“Έ 수 μžˆλ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.
(λ¬Όλ‘  UWP 및 WinUI 3의 κ°œλ°œμžκ°€ 생각보닀 λ§Žμ§€ μ•Šλ‹€λŠ” 점은 ν•¨μ μž…λ‹ˆλ‹€. γ…‘.,γ…‘)

μ–΄μ¨Œλ“  κ·Έλž˜μ„œ WinUI 개발자라면 Uno ν”Œλž«νΌμ˜ UI μž‘μ—…μ— ν—ˆλ“€μ΄ μ—†μŠ΅λ‹ˆλ‹€. .NET Blazorμ—μ„œ html및 cssλ₯Ό λ§Œμ Έμ•Ό ν•˜λŠ” 고톡이 μžˆλŠ” 뢄은 Uno ν”Œλž«νΌμ„ μΆ”μ²œν•©λ‹ˆλ‹€.

1개의 μ’‹μ•„μš”

μ‹œκ°„μ΄ κ½€ ν˜λ €μŠ΅λ‹ˆλ‹€. μš”μ¦˜μ—λŠ” 일주일이 ν•˜λ£¨ μ§€λ‚˜κ°€λŠ” 것 마λƒ₯ μ§€λ‚˜κ°€λ„€μš”. 벌써 3월도 λλ‚˜κ°€κ³  μžˆμŠ΅λ‹ˆλ‹€.

UnoλŠ” UWPλ₯Ό 이미 ν•œ 뢄이라면 거의 적응 κΈ°κ°„ 없이 개발 진행이 κ°€λŠ₯ν•©λ‹ˆλ‹€. Microsoft.UI.Xaml.Controls에 ν˜Έν™˜λ˜λŠ” XAML 및 μ»¨νŠΈλ‘€μ„ κ·ΈλŒ€λ‘œ μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€. λ˜ν•œ WinUI 3을 κ²½ν—˜ν–ˆλ‹€λ©΄ λ§ˆμ°¬κ°€μ§€λ‘œ λ°”λ‘œ 개발이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

ν™”λ©΄ λ ˆμ΄μ•„μ›ƒμ„ κ΅¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

image

λ©”μ‹œμ§€ λͺ©λ‘μ€ 1만개둜 λ°μŠ€ν¬ν†± μ•± ν˜•νƒœλ‘œ 거의 μ¦‰μ‹œ ν‘œμ‹œλ˜λŠ” 것을 μ•Œ 수 μžˆμ—ˆκ³  μ›Ήμ–΄μ…ˆλΈ”λ¦¬λ‘œλ„ μƒλ‹Ήνžˆ λΉ λ₯΄κ²Œ ν‘œμ‹œλ˜λŠ” 것을 확인할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μ˜€λŠ˜μ€ λ©”μ‹œμ§€ μž…λ ₯ μ˜μ—­μ„ μ‘°λͺ…ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

image

이 λΆ€λΆ„μΈλ°μš”, νŠΉλ³„ν•œ 것은 μ—†μ§€λ§Œ λ‚˜μ€‘μ„ μœ„ν•΄ μ»΄ν¬λ„ŒνŠΈν™” ν–ˆμŠ΅λ‹ˆλ‹€.

| MessageInput.xaml

<UserControl
    x:Class="DnSE.Controls.MessageInput"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:DnSE.Controls"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid MinHeight="64" MaxHeight="300">
        <ScrollViewer Grid.Column="0">
            <TextBox
                x:Name="messageTextBox"
                AcceptsReturn="True"
                Text="{x:Bind Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                TextWrapping="Wrap">
            </TextBox>
        </ScrollViewer>

        <SplitButton
            x:Name="sendButton"
            HorizontalAlignment="Right"
            VerticalAlignment="Bottom"
            Command="{x:Bind SendCommand}">
            <SymbolIcon Symbol="Send" />
        </SplitButton>
    </Grid>
</UserControl>

| MessageInput.xaml.cs

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

using System.Windows.Input;

namespace DnSE.Controls;

public sealed partial class MessageInput : UserControl
{
    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        nameof(Message),
        typeof(string),
        typeof(MessageInput),
        new PropertyMetadata(string.Empty, OnMessageChanged)
        );

    public static readonly DependencyProperty SendCommandProperty = DependencyProperty.Register(
        nameof(SendCommand),
        typeof(ICommand),
        typeof(MessageInput),
        new PropertyMetadata(null)
        );

    public string Message
    {
        get => (string)GetValue(MessageProperty);
        set => SetValue(MessageProperty, value);
    }

    public ICommand? SendCommand
    {
        get => (ICommand)GetValue(SendCommandProperty);
        set => SetValue(SendCommandProperty, value);
    }

    public MessageInput()
    {
        this.InitializeComponent();

        sendButton.IsEnabled = false;
    }

    private static void OnMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var @this = (d as MessageInput)!;
        if (string.IsNullOrEmpty(e.NewValue.ToString()) is true)
            @this.sendButton.IsEnabled = false;
        else
            @this.sendButton.IsEnabled = true;
    }
}

Message 및 SendCommandλ₯Ό 바인딩 ν•  수 μžˆμ–΄μ„œ λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

| MainPage.xaml

<controls:MessageInput Message="{x:Bind ViewModel.ChatMessage, Mode=TwoWay}" SendCommand="{x:Bind ViewModel.SendCommand}" />

일반적인 XAML의 바인딩과 λ™μΌν•˜μ£ ?

5개의 μ’‹μ•„μš”

@dimohy μ•„λΉ μ™€λ‚˜ μ—°μž¬ 기닀리고 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. :smile:

2개의 μ’‹μ•„μš”

UNO λ₯Ό 선택 ν•˜μ‹  νŠΉλ³„ν•œ μ΄μœ κ°€ μžˆλŠ”μ§€ ꢁ금 ν•©λ‹ˆλ‹€.
MAUI λŠ” 선택지에 μ—†μœΌμ…¨λŠ”μ§€λ„ ꢁ금 ν•˜κ΅¬μš” ?

UNO λŠ” λͺ¨λ°”일 , μœˆλ„μš° μ „λΆ€ 컀버 λ˜λ‚˜μš” ?

3개의 μ’‹μ•„μš”

μ›Ή 및 μ•±μ—μ„œ λ™μž‘ν•˜κΈ°λ₯Ό λ°”λΌλŠ”λ° 이것을 κ°€μž₯ 잘 μ§€μ›ν•˜λŠ” 것이 Unoμž…λ‹ˆλ‹€. MAUI의 경우 웹은 μ•ˆλ˜μ–΄μš”.

λ„΅

3개의 μ’‹μ•„μš”

헐…λͺ°λžλ„€μš”β€¦κ°μ‚¬ν•©λ‹ˆλ‹€.
UNO

3개의 μ’‹μ•„μš”

μžλ£Œκ°€ κ½€ 많이 λ‚˜μ˜€μ§€λ§Œ, .NET으둜 ν¬λ‘œμŠ€ν”Œλž«νΌμ„ ν•˜λŠ” λ°©λ²•μ—λŠ” μ—¬λŸ¬κ°€μ§€κ°€ μžˆμŠ΅λ‹ˆλ‹€

MAUIλŠ” κ·Έλƒ₯ MS곡식이라 유λͺ…ν•œ 것이고 κ²°μ •μ μœΌλ‘œ Linux Desktopμ—μ„œ μ•ˆλ©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ Uno Platformκ³Ό AvaloniaUIλŠ” Flutter처럼 λ‹€κΈ°μ’… OSμ—μ„œ λ™μž‘ν•©λ‹ˆλ‹€.

Flutter 같이 ν˜Έν™˜μ΄ 쒋은 κ²ƒκ³ΌλŠ” λ³„κ°œμ§€λ§Œ λ˜κΈ΄β€¦ ν•©λ‹ˆλ‹€.

2개의 μ’‹μ•„μš”

MAUI κ°€ μ•ˆλ“œλ‘œμ΄λ“œλŠ” λ˜μžλ‚˜μš”
그런데 λ¦¬λˆ…μŠ€ 데탑은 μ•ˆλ˜λ‚˜μš” ?

3개의 μ’‹μ•„μš”

λ„€ μ œκ°€ μ•ŒκΈ°λ‘  μ•ˆλ©λ‹ˆλ‹€.

μΆ”κ°€μ μœΌλ‘œ MAUIλŠ” Pixel Perfect κ°€ μ•„λ‹™λ‹ˆλ‹€.

Uno Platformκ³Ό AvaloniaUIλŠ” Pixel Perfect μž…λ‹ˆλ‹€.

Slack처럼 OSλ₯Ό 변경해도 λ˜‘κ°™μ΄ μƒκ²Όλ‹€λŠ” λœ»μž…λ‹ˆλ‹€.

3개의 μ’‹μ•„μš”

MVVM ꡬ쑰둜 λ§Œλ“€κΈ° μœ„ν•΄ Uno ν”„λ‘œμ νŠΈ ν…œν”Œλ¦Ώμ—μ„œ μ΅œκ·Όμ— λ„μž…λœ λ§ˆλ²•μ‚¬ κΈ°λŠ₯을 ν™œμš©ν•΄ 보렀 ν–ˆλŠ”λ° {x:bind}λŠ” μ§€μ›ν•˜μ§€ μ•ŠλŠ” 것 κ°™μ•„ (아직 λͺ°λΌμ„œμΌ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€) κ·Έλƒ₯ Community Toolkit MVVM의 Iocλ₯Ό μ΄μš©ν–ˆμŠ΅λ‹ˆλ‹€.

| App.cs

public class App : Application
{
    public App()
    {
        Ioc.Default.ConfigureServices(ConfigureServices());
    }

    ...

    private IServiceProvider ConfigureServices() => new ServiceCollection()
        .AddTransient<MainViewModel>()
        .BuildServiceProvider();
}

| MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        private MainViewModel ViewModel { get; } = Ioc.Default.GetRequiredService<MainViewModel>();

        public MainPage()
        {
            this.InitializeComponent();
        }
    }
1개의 μ’‹μ•„μš”

ν‹ˆν‹ˆνžˆβ€¦ λ§Œλ“€μ–΄κ°€κ³  μžˆμŠ΅λ‹ˆλ‹€β€¦

image

6개의 μ’‹μ•„μš”