Como posso salvar as configurações do aplicativo em um aplicativo Windows Forms?

582

O que eu quero alcançar é muito simples: eu tenho um aplicativo Windows Forms (.NET 3.5) que usa um caminho para ler informações. Esse caminho pode ser modificado pelo usuário, usando o formulário de opções que forneço.

Agora, quero salvar o valor do caminho em um arquivo para uso posterior. Essa seria uma das muitas configurações salvas neste arquivo. Este arquivo fica diretamente na pasta do aplicativo.

Entendo que três opções estão disponíveis:

  • Arquivo ConfigurationSettings (appname.exe.config)
  • Registro
  • Arquivo XML Customizado

Eu li que o arquivo de configuração do .NET não está previsto para salvar os valores novamente. Quanto ao registro, gostaria de ficar o mais longe possível.

Isso significa que devo usar um arquivo XML personalizado para salvar as configurações?

Nesse caso, eu gostaria de ver um exemplo de código disso (C #).

Já vi outras discussões sobre esse assunto, mas ainda não está claro para mim.

Abastecido
fonte
Este é um aplicativo .NET WinForms? Em caso afirmativo, em qual versão do .NET você está desenvolvendo?
Portman
1
Sim, é um aplicativo WinForms do .NET framework versão 3.5.
Alimentada
1
você precisa salvar senhas ou valores de segredos ? Talvez exige que qualquer criptografia
Kiquenet

Respostas:

593

Se você trabalha com o Visual Studio, é muito fácil obter configurações persistentes. Clique com o botão direito do mouse no projeto no Solution Explorer e escolha Propriedades. Selecione a guia Configurações e clique no hiperlink se as configurações não existirem.

Use a guia Configurações para criar configurações do aplicativo. O Visual Studio cria os arquivos Settings.settingse Settings.Designer.settingsque contêm a classe singleton Settingsherdada de ApplicationSettingsBase . Você pode acessar esta classe a partir do seu código para ler / gravar as configurações do aplicativo:

Properties.Settings.Default["SomeProperty"] = "Some Value";
Properties.Settings.Default.Save(); // Saves settings in application configuration file

Essa técnica é aplicável tanto ao console, Windows Forms e outros tipos de projeto.

Observe que você precisa definir a propriedade do escopo das suas configurações. Se você selecionar o escopo do aplicativo, Settings.Default. <Sua propriedade> será somente leitura.

Referência: Como: Escrever configurações do usuário em tempo de execução com C # - Microsoft Docs

aku
fonte
2
se eu tiver uma solução, isso se aplicará a toda a solução ou a cada projeto?
franko_camron
8
@ Quatro: Eu tenho um projeto .NET 4.0 WinApp aqui, e meu SomeProperty não é somente leitura. Settings.Default.SomeProperty = 'value'; Settings.Default.Save();Funciona como um encanto. Ou é porque eu tenho configurações de usuário?
Dokman
4
@Quatro: quando mudei uma configuração do Usuário para o escopo do aplicativo e salve o arquivo, vi no código gerado que o setter desapareceu. Isso também acontece com o perfil do cliente 4.0 ...
doekman 17/01/12
3
@ Quatro: ótimo link, embora a sua declaração de que Settings.Default.Save()não faz nada está incorreta. Como @aku afirma na resposta, as configurações do escopo do aplicativo são somente leitura: save é ineficaz para eles. Use esse PortableSettingsProvider personalizado para salvar as configurações do escopo do usuário no app.config localizado onde o exe está, em vez daquele na pasta AppData do usuário. Não, geralmente não é bom, mas eu o uso durante o desenvolvimento para usar as mesmas configurações de compilar para compilar (sem ele, elas vão para novas pastas de usuário exclusivas a cada compilação).
minnow
7
A partir de agora, com o .NET 3.5, parece que você pode usar o Settings.Default.SomeProperty para atribuir um valor e obter uma conversão de texto forte. Além disso, para economizar tempo de outras pessoas (demorei um pouco para descobrir isso), você deve digitar Properties.Settings.Default ou adicionar usando YourProjectNameSpace.Settings na parte superior do arquivo. Somente "Configurações" não são definidas / encontradas.
eselk
95

Se você está planejando salvar em um arquivo no mesmo diretório que o executável, aqui está uma boa solução que usa o formato JSON :

using System;
using System.IO;
using System.Web.Script.Serialization;

namespace MiscConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            MySettings settings = MySettings.Load();
            Console.WriteLine("Current value of 'myInteger': " + settings.myInteger);
            Console.WriteLine("Incrementing 'myInteger'...");
            settings.myInteger++;
            Console.WriteLine("Saving settings...");
            settings.Save();
            Console.WriteLine("Done.");
            Console.ReadKey();
        }

        class MySettings : AppSettings<MySettings>
        {
            public string myString = "Hello World";
            public int myInteger = 1;
        }
    }

    public class AppSettings<T> where T : new()
    {
        private const string DEFAULT_FILENAME = "settings.json";

        public void Save(string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(this));
        }

        public static void Save(T pSettings, string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(pSettings));
        }

        public static T Load(string fileName = DEFAULT_FILENAME)
        {
            T t = new T();
            if(File.Exists(fileName))
                t = (new JavaScriptSerializer()).Deserialize<T>(File.ReadAllText(fileName));
            return t;
        }
    }
}
Trevor
fonte
Sim, altere DEFAULT_FILENAME para um caminho absoluto se desejar salvar em outro diretório. Eu acho que é mais comum salvar o arquivo no mesmo diretório que o aplicativo ou em um subdiretório, se você não estiver salvando no registro.
Trevor
Ah, talvez a melhor opção seja armazenar o arquivo de configurações na pasta appdata do usuário.
Trevor
1
Não há necessidade de mudar DEFAULT_FILENAME, basta ligar settings.Save(theFileToSaveTo); Sendo todas as letras maiúsculas, DEFAULT_FILENAMEé suposto ser uma constante . Se você deseja uma propriedade de leitura e gravação, faça uma e faça com que o construtor a configure DEFAULT_FILENAME. Em seguida, faça com que o valor padrão do argumento seja null, teste para isso e use sua propriedade como o valor padrão. É um pouco mais digitado, mas oferece uma interface mais padrão.
Jesse Chisholm
10
Você precisará fazer referência System.Web.Extensions.dllse ainda não o fez.
TEK 30/03
9
Criei uma biblioteca inteira com base nesta resposta com muitas melhorias e disponibilizei-a no nuget: github.com/Nucs/JsonSettings
NucS
67

O registro é proibido. Você não tem certeza se o usuário que usa seu aplicativo tem direitos suficientes para gravar no registro.

Você pode usar o app.configarquivo para salvar as configurações no nível do aplicativo (que são iguais para cada usuário que usa seu aplicativo).

Eu armazenaria configurações específicas do usuário em um arquivo XML, que seria salvo no armazenamento isolado ou no diretório SpecialFolder.ApplicationData .

Além disso, a partir do .NET 2.0, é possível armazenar valores novamente no app.configarquivo.

Frederik Gheysels
fonte
8
Use o registro, no entanto, se desejar configurações por login / usuário.
thenonhacker
19
O registro não é portável
Kb.
10
@thenonhacker: Ou uso Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData)
Kenny Mann
4
O registro do usuário pode ser gravado (muitos programas gravam informações lá e as permissões do usuário nunca são um problema). A vantagem de usar o registro em vez de usar configurações é que, se você tiver vários aplicativos compartilhando a mesma pasta (por exemplo, um programa de instalação e um programa de aplicativo), eles não compartilharão as mesmas configurações.
Kesty
3
A principal desvantagem do Registro é a maneira mais difícil de exportar / copiar as configurações para outro PC. Mas não concordo com "Você não tem certeza se o usuário que usa seu aplicativo tem direitos suficientes para gravar no registro" - em HKEY_CURRENT_USER, você sempre tem direitos para escrever. Pode ser negado, mas o sistema de arquivos também pode estar inacessível para o usuário atual (todas as pastas TEMP possíveis, etc.).
i486
20

A ApplicationSettingsclasse não suporta salvar configurações no arquivo app.config . Isso é muito por design; os aplicativos executados com uma conta de usuário devidamente protegida (pense no Vista UAC) não têm acesso de gravação à pasta de instalação do programa.

Você pode combater o sistema com a ConfigurationManagerclasse. Mas a solução trivial é entrar no designer de Configurações e alterar o escopo da configuração para Usuário. Se isso causar dificuldades (por exemplo, a configuração é relevante para todos os usuários), você deve colocar o recurso Opções em um programa separado para poder solicitar o prompt de elevação de privilégios. Ou renuncie usando uma configuração.

Hans Passant
fonte
Você poderia expandir sua última frase? Peça a elevação para escrever app.config ou para escrever um aplicativo separado que percorra a página inicial de todos os usuários, procure user.config e edite-os?
CannibalSmith
2
O programa separado requer um manifesto para solicitar elevação. O Google 'asinvoker exigem administrador' para encontrar a sintaxe adequada. Editar user.config não é prático nem necessário.
Hans Passant
18

O argumento de registro / configurationSettings / XML ainda parece muito ativo. Eu usei todos eles, à medida que a tecnologia avançava, mas o meu favorito é baseado no sistema da Threed combinado com o armazenamento isolado .

O exemplo a seguir permite o armazenamento de objetos nomeados propriedades em um arquivo no armazenamento isolado. Tal como:

AppSettings.Save(myobject, "Prop1,Prop2", "myFile.jsn");

As propriedades podem ser recuperadas usando:

AppSettings.Load(myobject, "myFile.jsn");

É apenas uma amostra, não sugestiva de boas práticas.

internal static class AppSettings
{
    internal static void Save(object src, string targ, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = src.GetType();

        string[] paramList = targ.Split(new char[] { ',' });
        foreach (string paramName in paramList)
            items.Add(paramName, type.GetProperty(paramName.Trim()).GetValue(src, null));

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify.
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Create, storage))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                writer.Write((new JavaScriptSerializer()).Serialize(items));
            }

        }
        catch (Exception) { }   // If fails - just don't use preferences
    }

    internal static void Load(object tar, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = tar.GetType();

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, storage))
            using (StreamReader reader = new StreamReader(stream))
            {
                items = (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(reader.ReadToEnd());
            }
        }
        catch (Exception) { return; }   // If fails - just don't use preferences.

        foreach (KeyValuePair<string, object> obj in items)
        {
            try
            {
                tar.GetType().GetProperty(obj.Key).SetValue(tar, obj.Value, null);
            }
            catch (Exception) { }
        }
    }
}
Boczek
fonte
1
Ou melhor ainda; use DataContractJsonSerializer
Boczek
16

Eu queria compartilhar uma biblioteca que construí para isso. É uma pequena biblioteca, mas uma grande melhoria (IMHO) sobre arquivos .settings.

A biblioteca é chamada Jot (GitHub) . Aqui está um artigo antigo do The Code Project que escrevi sobre isso.

Veja como você o usaria para acompanhar o tamanho e a localização de uma janela:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

O benefício comparado aos arquivos .settings: há consideravelmente menos código e é muito menos suscetível a erros, pois você só precisa mencionar cada propriedade uma vez .

Com os arquivos de configurações, é necessário mencionar cada propriedade cinco vezes: uma vez quando você cria explicitamente a propriedade e outras quatro vezes no código que copia os valores para frente e para trás.

Armazenamento, serialização, etc. são completamente configuráveis. Quando os objetos de destino são criados por um contêiner de IoC , você pode [conectá-lo] [] para que ele aplique o rastreamento automaticamente a todos os objetos que resolve, para que tudo o que você precise fazer para tornar uma propriedade persistente seja clicar em [Rastreável] atributo nele.

É altamente configurável e você pode configurar: - quando os dados são persistentes e aplicados globalmente ou para cada objeto rastreado - como são serializados - onde são armazenados (por exemplo, arquivo, banco de dados, armazenamento online, isolado, registro) - regras que podem cancelar a aplicação / dados persistentes para uma propriedade

Confie em mim, a biblioteca é de primeira qualidade!

anakic
fonte
14

Uma maneira simples é usar um objeto de dados de configuração, salvá-lo como um arquivo XML com o nome do aplicativo na pasta local e, na inicialização, lê-lo novamente.

Aqui está um exemplo para armazenar a posição e o tamanho de um formulário.

O objeto de dados de configuração é fortemente digitado e fácil de usar:

[Serializable()]
public class CConfigDO
{
    private System.Drawing.Point m_oStartPos;
    private System.Drawing.Size m_oStartSize;

    public System.Drawing.Point StartPos
    {
        get { return m_oStartPos; }
        set { m_oStartPos = value; }
    }

    public System.Drawing.Size StartSize
    {
        get { return m_oStartSize; }
        set { m_oStartSize = value; }
    }
}

Uma classe de gerente para salvar e carregar:

public class CConfigMng
{
    private string m_sConfigFileName = System.IO.Path.GetFileNameWithoutExtension(System.Windows.Forms.Application.ExecutablePath) + ".xml";
    private CConfigDO m_oConfig = new CConfigDO();

    public CConfigDO Config
    {
        get { return m_oConfig; }
        set { m_oConfig = value; }
    }

    // Load configuration file
    public void LoadConfig()
    {
        if (System.IO.File.Exists(m_sConfigFileName))
        {
            System.IO.StreamReader srReader = System.IO.File.OpenText(m_sConfigFileName);
            Type tType = m_oConfig.GetType();
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            object oData = xsSerializer.Deserialize(srReader);
            m_oConfig = (CConfigDO)oData;
            srReader.Close();
        }
    }

    // Save configuration file
    public void SaveConfig()
    {
        System.IO.StreamWriter swWriter = System.IO.File.CreateText(m_sConfigFileName);
        Type tType = m_oConfig.GetType();
        if (tType.IsSerializable)
        {
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            xsSerializer.Serialize(swWriter, m_oConfig);
            swWriter.Close();
        }
    }
}

Agora você pode criar uma instância e usar os eventos load e close do seu formulário:

    private CConfigMng oConfigMng = new CConfigMng();

    private void Form1_Load(object sender, EventArgs e)
    {
        // Load configuration
        oConfigMng.LoadConfig();
        if (oConfigMng.Config.StartPos.X != 0 || oConfigMng.Config.StartPos.Y != 0)
        {
            Location = oConfigMng.Config.StartPos;
            Size = oConfigMng.Config.StartSize;
        }
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        // Save configuration
        oConfigMng.Config.StartPos = Location;
        oConfigMng.Config.StartSize = Size;
        oConfigMng.SaveConfig();
    }

E o arquivo XML produzido também é legível:

<?xml version="1.0" encoding="utf-8"?>
<CConfigDO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <StartPos>
    <X>70</X>
    <Y>278</Y>
  </StartPos>
  <StartSize>
    <Width>253</Width>
    <Height>229</Height>
  </StartSize>
</CConfigDO>
Dieter Meemken
fonte
1
Eu tenho esse trabalho excelente no desenvolvimento, mas quando implanto o aplicativo, o usuário comum não tem acesso à c:\program files\my applicationpasta; portanto, o salvamento das configurações gera um erro. Estou tentando salvar o arquivo xml no AppData, mas apenas me perguntei se havia uma maneira óbvia de contornar esse problema, pois essa abordagem parece ter funcionado para você.
Philip Stratford
@ PhillipStratford Como é apenas um arquivo normal, você pode salvá-lo em qualquer lugar. Basta encontrar um lugar com acesso de gravação.
Dieter Meemken
@PhilipStratford Pode ser que a pasta AppData seja uma opção para você, consulte C # obtendo o caminho de% AppData% , conforme mencionado pelo kite .
Dieter Meemken 6/03/2017
Obrigado, eu já implementei isso, salvando o arquivo xml na pasta AppDate. Eu apenas me perguntei se havia uma maneira fácil de salvá-lo na pasta do aplicativo, como no seu exemplo, desde que eu assumi que você o havia feito funcionar. Não se preocupe, a pasta AppData provavelmente é um local melhor!
Philip Stratford
5

Outras opções, em vez de usar um arquivo XML personalizado, podemos usar um formato de arquivo mais amigável: arquivo JSON ou YAML.

  • Se você usa o .NET 4.0 dinâmico, essa biblioteca é realmente fácil de usar (serializar, desserializar, objetos aninhados suportam e ordenam a saída como você deseja + mesclando várias configurações em uma) JsonConfig (o uso é equivalente a ApplicationSettingsBase)
  • Para a biblioteca de configuração .NET YAML ... Não encontrei uma que seja tão fácil de usar quanto o JsonConfig

Você pode armazenar seu arquivo de configurações em várias pastas especiais (para todos os usuários e por usuário), conforme listado aqui Environment.SpecialFolder Enumeração e vários arquivos (somente leitura padrão, por função, por usuário, etc.)

Se você optar por usar várias configurações, poderá mesclar essas configurações: Por exemplo, mesclando configurações para o padrão + BasicUser + AdminUser. Você pode usar suas próprias regras: a última substitui o valor, etc.

pipa
fonte
4

"Isso significa que devo usar um arquivo XML personalizado para salvar as configurações?" Não, não necessariamente. Usamos o SharpConfig para essas operações.

Por exemplo, se um arquivo de configuração é assim

[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment

Podemos recuperar valores como este

var config = Configuration.LoadFromFile("sample.cfg");
var section = config["General"];

string someString = section["SomeString"].StringValue;
int someInteger = section["SomeInteger"].IntValue;

É compatível com o .NET 2.0 e superior. Podemos criar arquivos de configuração em tempo real e salvá-los mais tarde.

Fonte: http://sharpconfig.net/
GitHub: https://github.com/cemdervis/SharpConfig

Turker Tunali
fonte
3

Pelo que sei, o .NET suporta configurações persistentes usando o recurso de configurações de aplicativos interno:

O recurso Configurações do aplicativo do Windows Forms facilita a criação, o armazenamento e a manutenção de preferências personalizadas de aplicativos e usuários no computador cliente. Com as configurações do aplicativo Windows Forms, você pode armazenar não apenas dados do aplicativo, como cadeias de conexão de banco de dados, mas também dados específicos do usuário, como preferências do aplicativo do usuário. Usando o Visual Studio ou código gerenciado personalizado, você pode criar novas configurações, lê-las e gravá-las no disco, vinculá-las às propriedades dos formulários e validar os dados das configurações antes de carregar e salvar. - http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx

Jacob
fonte
2
Não é verdade .. veja a resposta do aku acima. é possível usando Settings e ApplicationSettingsBase
Gishu
2

Às vezes, você deseja se livrar dessas configurações mantidas no arquivo tradicional web.config ou app.config. Você deseja um controle mais refinado sobre a implantação de suas entradas de configurações e o design de dados separados. Ou o requisito é permitir a adição de novas entradas no tempo de execução.

Eu posso imaginar duas boas opções:

  • A versão fortemente tipada e
  • A versão orientada a objetos.

A vantagem da versão fortemente tipada são os nomes e valores das configurações fortemente tipadas. Não há risco de misturar nomes ou tipos de dados. A desvantagem é que mais configurações precisam ser codificadas, não podem ser adicionadas em tempo de execução.

Com a versão orientada a objeto, a vantagem é que novas configurações podem ser adicionadas em tempo de execução. Mas você não possui nomes e valores fortemente digitados. Deve ter cuidado com os identificadores de sequência. É necessário conhecer o tipo de dados salvo anteriormente ao obter um valor.

Você pode encontrar o código de ambas as implementações totalmente funcionais AQUI .

user3130351
fonte
2

Sim, é possível salvar a configuração - mas depende muito da maneira como você escolhe fazê-lo. Deixe-me descrever as diferenças técnicas para que você possa entender as opções que possui:

Primeiro, você precisa distinguir se deseja usar applicationSettings ou AppSettings no seu arquivo *.exe.config(também conhecido como App.configVisual Studio) - há diferenças fundamentais, sendo descritas aqui .

Ambos oferecem maneiras diferentes de salvar alterações:

  • O AppSettings permite ler e gravar diretamente no arquivo de configuração (via config.Save(ConfigurationSaveMode.Modified);, onde a configuração é definida como config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);).

  • O applicationSettings permite ler, mas se você escrever alterações (via Properties.Settings.Default.Save();), elas serão gravadas por usuário, armazenadas em um local especial (por exemplo C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0). Como Hans Passant mencionou em sua resposta, isso ocorre porque um usuário geralmente tem direitos restritos aos Arquivos de Programas e não pode gravar nele sem chamar o prompt do UAC. Uma desvantagem é que, se você adicionar chaves de configuração no futuro, precisará sincronizá-las com todos os perfis de usuário.

Nota: Conforme mencionado na pergunta, existe uma terceira opção: se você tratar o arquivo de configuração como documento XML, poderá carregá-lo, modificá-lo e salvá-lo usando a System.Xml.Linq.XDocumentclasse Não é necessário usar um arquivo XML personalizado, você pode ler o arquivo de configuração existente; para consultar elementos, você pode até usar consultas Linq. Dei um exemplo aqui , confira a função GetApplicationSettingna resposta.

Se você precisar de criptografia para proteger seus valores, confira esta resposta.

Matt
fonte
Obrigado por esta excelente resposta.
NoChance 27/01
2
@NoChance - De nada, feliz por eu poder ajudá-lo!
Matt
Agradável! Finalmente descobri tudo 😂😂😂
Momoro
1
@Momoro - bom ouvir! ;-)
Matt
1
public static class SettingsExtensions
{
    public static bool TryGetValue<T>(this Settings settings, string key, out T value)
    {
        if (settings.Properties[key] != null)
        {
            value = (T) settings[key];
            return true;
        }

        value = default(T);
        return false;
    }

    public static bool ContainsKey(this Settings settings, string key)
    {
        return settings.Properties[key] != null;
    }

    public static void SetValue<T>(this Settings settings, string key, T value)
    {
        if (settings.Properties[key] == null)
        {
            var p = new SettingsProperty(key)
            {
                PropertyType = typeof(T),
                Provider = settings.Providers["LocalFileSettingsProvider"],
                SerializeAs = SettingsSerializeAs.Xml
            };
            p.Attributes.Add(typeof(UserScopedSettingAttribute), new UserScopedSettingAttribute());
            var v = new SettingsPropertyValue(p);
            settings.Properties.Add(p);
            settings.Reload();
        }
        settings[key] = value;
        settings.Save();
    }
}
Paul - Soura Tech LLC
fonte